class GeneLocus(Model): """ Basically a gene as defined by a standard reference, it's location, length and other details which are optional. """ STRANDS = ( (None, 'Undefined'), ('+', '+'), ('-', '-'), ('.', '.'), ) GENE_TYPES = ( ('P', 'Promoter'), ('C', 'Coding'), ('I', 'Intergenic'), ('R', 'RNA'), ) genome = ForeignKey(Genome, related_name='gene_locuses', null=True, blank=True) name = CharField(max_length=255, db_index=True) previous_id = CharField(max_length=64, db_index=True, null=True, blank=True) start = IntegerField(blank=True, null=True) stop = IntegerField(blank=True, null=True) length = IntegerField(blank=True, null=True) strand = CharField(max_length=5, choices=STRANDS, blank=True, null=True) gene_type = CharField(max_length=1, choices=GENE_TYPES, blank=True, null=True, help_text="Basic coding type for the locus.") gene_symbol = CharField(max_length=32, blank=True, null=True, help_text="Short identifier used in names") description = CharField(max_length=255, blank=True, null=True, help_text="Basic description about the gene.") gene_ontology = CharField( max_length=255, blank=True, null=True, help_text="Gene ontology or GO-Terms are annotations in the GO format" " that describe the gene product in a predictable way.") enzyme_commission = CharField( max_length=255, blank=True, null=True, help_text="The Enzyme Commission numbers for this gene.") pathway_kegg = CharField(max_length=255, blank=True, null=True, help_text="The KEGG based pathways") pathway_cyc = CharField( max_length=255, blank=True, null=True, help_text="The PWY numbers usually linking to MetaCyc") pathway_cog = CharField( max_length=255, blank=True, null=True, help_text="Clusters of Orthologous Groups of protein list") protein_families = CharField(max_length=255, blank=True, null=True, help_text="Protein families from PFAM") objects = GeneLocusManager() class Meta: ordering = ('name', ) unique_together = ('genome', 'name') def natural_key(self): """The genome's short code is a useful key to lookup this table""" return (self.genome.code, self.name) def __str__(self): return self.name
class CompositionEDTF(Model): composition = ParentalKey('CompositionPage', on_delete=CASCADE, unique=True, related_name='date') nat_lang_edtf_string = CharField( verbose_name='Natural Language Date', help_text=('The EDTF date in natural language. This field is help ' 'users who aren\'t familiar with the EDTF. It does not ' 'change how the date is represented.'), max_length=256) edtf_string = CharField( verbose_name='EDTF Date', help_text=mark_safe( 'A date in the <a href="https://www.loc.gov/standards/datetime/" ' 'target="_blank"><strong>Extended Date Time Format</strong></a>'), max_length=256) lower_fuzzy = DateField(editable=False) upper_fuzzy = DateField(editable=False) lower_strict = DateField(editable=False) upper_strict = DateField(editable=False) nat_lang_year = CharField(editable=False, max_length=9) panels = [FieldPanel('edtf_string'), FieldPanel('nat_lang_edtf_string')] def __str__(self): return self.edtf_string def clean(self): try: e = parse_edtf(self.edtf_string) except EDTFParseException: raise ValidationError({ 'edtf_string': '{} is not a valid EDTF string'.format(self.edtf_string) }) self.lower_fuzzy = struct_time_to_date(e.lower_fuzzy()) self.upper_fuzzy = struct_time_to_date(e.upper_fuzzy()) self.lower_strict = struct_time_to_date(e.lower_strict()) self.upper_strict = struct_time_to_date(e.upper_strict()) if self.lower_strict.year != self.upper_strict.year: self.nat_lang_year = '{}-{}'.format(self.lower_strict.year, self.upper_strict.year) else: self.nat_lang_year = str(self.lower_strict.year) def save(self, *args, **kwargs): try: e = parse_edtf(self.edtf_string) except EDTFParseException: raise ValidationError('{} is not a valid EDTF string'.format( self.edtf_string)) self.lower_fuzzy = struct_time_to_date(e.lower_fuzzy()) self.upper_fuzzy = struct_time_to_date(e.upper_fuzzy()) self.lower_strict = struct_time_to_date(e.lower_strict()) self.upper_strict = struct_time_to_date(e.upper_strict()) if self.lower_strict.year != self.upper_strict.year: self.nat_lang_year = '{}-{}'.format(self.lower_strict.year, self.upper_strict.year) else: self.nat_lang_year = str(self.lower_strict.year) super().save(*args, **kwargs)
class Instrument(Model): instrument = CharField(max_length=256) def __str__(self): return self.instrument
class Topic(BasePage): resource_type = "topic" parent_page_types = ["Topics"] subpage_types = ["Topic"] template = "topic.html" # Content fields description = RichTextField( blank=True, default="", features=RICH_TEXT_FEATURES_SIMPLE, help_text="Optional short text description, max. 400 characters", max_length=400, ) featured = StreamField( StreamBlock( [ ( "post", PageChooserBlock(target_model=( "articles.Article", "externalcontent.ExternalArticle", )), ), ("external_page", FeaturedExternalBlock()), ], max_num=4, required=False, ), null=True, blank=True, help_text="Optional space for featured posts, max. 4", ) tabbed_panels = StreamField( StreamBlock([("panel", TabbedPanelBlock())], max_num=3, required=False), null=True, blank=True, help_text= "Optional tabbed panels for linking out to other resources, max. 3", verbose_name="Tabbed panels", ) latest_articles_count = IntegerField( choices=RESOURCE_COUNT_CHOICES, default=3, help_text="The number of posts to display for this topic.", ) # Card fields card_title = CharField("Title", max_length=140, blank=True, default="") card_description = TextField("Description", max_length=400, blank=True, default="") card_image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", verbose_name="Image", ) # Meta icon = FileField( upload_to="topics/icons", blank=True, default="", help_text=("MUST be a black-on-transparent SVG icon ONLY, " "with no bitmap embedded in it."), validators=[check_for_svg_file], ) color = CharField(max_length=14, choices=COLOR_CHOICES, default="blue-40") keywords = ClusterTaggableManager(through=TopicTag, blank=True) # Content panels content_panels = BasePage.content_panels + [ FieldPanel("description"), StreamFieldPanel("featured"), StreamFieldPanel("tabbed_panels"), FieldPanel("latest_articles_count"), MultiFieldPanel( [InlinePanel("people")], heading="People", help_text= "Optional list of people associated with this topic as experts", ), ] # Card panels card_panels = [ FieldPanel("card_title"), FieldPanel("card_description"), ImageChooserPanel("card_image"), ] # Meta panels meta_panels = [ MultiFieldPanel( [ InlinePanel("parent_topics", label="Parent topic(s)"), InlinePanel("child_topics", label="Child topic(s)"), ], heading="Parent/child topic(s)", classname="collapsible collapsed", help_text=("Topics with no parent (i.e. top-level topics) will be " "listed on the home page. Child topics are listed " "on the parent topic’s page."), ), MultiFieldPanel( [FieldPanel("icon"), FieldPanel("color")], heading="Theme", help_text=( "Theme settings used on topic page and any tagged content. " "For example, a post tagged with this topic " "will use the color specified here as its accent color."), ), MultiFieldPanel( [ FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("social_image"), FieldPanel("keywords"), ], heading="SEO", help_text=("Optional fields to override the default " "title and description for SEO purposes"), ), ] # Settings panels settings_panels = [FieldPanel("slug"), FieldPanel("show_in_menus")] # Tabs edit_handler = TabbedInterface([ ObjectList(content_panels, heading="Content"), ObjectList(card_panels, heading="Card"), ObjectList(meta_panels, heading="Meta"), ObjectList(settings_panels, heading="Settings", classname="settings"), ]) @property def articles(self): return get_combined_articles(self, topics__topic__pk=self.pk) @property def events(self): """Return upcoming events for this topic, ignoring events in the past, ordered by start date""" return get_combined_events(self, topics__topic__pk=self.pk, start_date__gte=datetime.datetime.now()) @property def experts(self): """Return Person instances for topic experts""" return [person.person for person in self.people.all()] @property def videos(self): """Return the latest videos and external videos for this topic. """ return get_combined_videos(self, topics__topic__pk=self.pk) @property def color_value(self): return dict(COLOR_VALUES)[self.color] @property def subtopics(self): return [topic.child for topic in self.child_topics.all()]
class Dog(ExportModelOperationsMixin('dog'), Model): name = CharField(max_length=100, unique=True) breed = CharField(max_length=100, blank=True, null=True) age = PositiveIntegerField(blank=True, null=True)
class BigCharSetModel(Model): field = SetTextField(base_field=CharField(max_length=8), max_length=32)
def filter_full_name(self, queryset, field_name, value): if value: queryset = queryset.annotate(fullname=Concat(F('group__slug'), V('/'), F('slug'), output_field=CharField())).filter(fullname__startswith=value) return queryset
class SharableLink(Model): PERMISSIONS_CHOICES = (('read', 'read'), ('read+write', 'read+write')) code = CharField(max_length=32, unique=True) permissions = CharField(max_length=10, choices=PERMISSIONS_CHOICES) expiry_date = DateTimeField() note = ForeignKey(Note, on_delete=CASCADE)
class Book(Model): title = CharField(max_length=32, db_index=True) author = ForeignKey(Author, on_delete=CASCADE, related_name="books")
class Request(models.Model): id = CharField(max_length=36, default=uuid4, primary_key=True) path = CharField(max_length=190, db_index=True) query_params = TextField(blank=True, default='') raw_body = TextField(blank=True, default='') body = TextField(blank=True, default='') method = CharField(max_length=10) start_time = DateTimeField(default=timezone.now, db_index=True) view_name = CharField(max_length=190, db_index=True, blank=True, default='', null=True) end_time = DateTimeField(null=True, blank=True) time_taken = FloatField(blank=True, null=True) encoded_headers = TextField(blank=True, default='') # stores json meta_time = FloatField(null=True, blank=True) meta_num_queries = IntegerField(null=True, blank=True) meta_time_spent_queries = FloatField(null=True, blank=True) pyprofile = TextField(blank=True, default='') prof_file = FileField(max_length=300, blank=True, storage=silk_storage, null=True) # Useful method to create shortened copies of strings without losing start and end context # Used to ensure path and view_name don't exceed 190 characters def _shorten(self, string): return '%s...%s' % (string[:94], string[len(string) - 93:]) @property def total_meta_time(self): return (self.meta_time or 0) + (self.meta_time_spent_queries or 0) @property def profile_table(self): for n, columns in enumerate(parse_profile(self.pyprofile)): location = columns[-1] if n and '{' not in location and '<' not in location: r = re.compile('(?P<src>.*\.py)\:(?P<num>[0-9]+).*') m = r.search(location) group = m.groupdict() src = group['src'] num = group['num'] name = 'c%d' % n fmt = '<a name={name} href="?pos={n}&file_path={src}&line_num={num}#{name}">{location}</a>' rep = fmt.format(**dict(group, **locals())) yield columns[:-1] + [mark_safe(rep)] else: yield columns # defined in atomic transaction within SQLQuery save()/delete() as well # as in bulk_create of SQLQueryManager # TODO: This is probably a bad way to do this, .count() will prob do? num_sql_queries = IntegerField(default=0) # TODO replace with count() @property def time_spent_on_sql_queries(self): """ TODO: Perhaps there is a nicer way to do this with Django aggregates? My initial thought was to perform: SQLQuery.objects.filter.aggregate(Sum(F('end_time')) - Sum(F('start_time'))) However this feature isnt available yet, however there has been talk for use of F objects within aggregates for four years here: https://code.djangoproject.com/ticket/14030. It looks like this will go in soon at which point this should be changed. """ return sum(x.time_taken for x in SQLQuery.objects.filter(request=self)) @property def headers(self): if self.encoded_headers: raw = json.loads(self.encoded_headers) else: raw = {} return CaseInsensitiveDictionary(raw) @property def content_type(self): return self.headers.get('content-type', None) @classmethod def garbage_collect(cls, force=False): """ Remove Request/Responses when we are at the SILKY_MAX_RECORDED_REQUESTS limit Note that multiple in-flight requests may call this at once causing a double collection """ check_percent = SilkyConfig().SILKY_MAX_RECORDED_REQUESTS_CHECK_PERCENT check_percent /= 100.0 if check_percent < random.random() and not force: return target_count = SilkyConfig().SILKY_MAX_RECORDED_REQUESTS # Since garbage collection is probabilistic, the target count should # be lowered to account for requests before the next garbage collection if check_percent != 0: target_count -= int(1 / check_percent) # Make sure we can delete everything if needed by settings if target_count <= 0: cls.objects.all().delete() return requests = cls.objects.order_by('-start_time') if not requests or len(requests) - 1 < target_count: return time_cutoff = requests[target_count].start_time cls.objects.filter(start_time__lte=time_cutoff).delete() def save(self, *args, **kwargs): # sometimes django requests return the body as 'None' if self.raw_body is None: self.raw_body = '' if self.body is None: self.body = '' if self.end_time and self.start_time: interval = self.end_time - self.start_time self.time_taken = interval.total_seconds() * 1000 # We can't save if either path or view_name exceed 190 characters if self.path and len(self.path) > 190: self.path = self._shorten(self.path) if self.view_name and len(self.view_name) > 190: self.view_name = self._shorten(self.view_name) super(Request, self).save(*args, **kwargs) Request.garbage_collect(force=False)
class Event(BasePage): resource_type = "event" parent_page_types = ["events.Events"] subpage_types = [] template = "event.html" # Content fields description = RichTextField( blank=True, default="", features=RICH_TEXT_FEATURES_SIMPLE, help_text="Optional short text description, max. 400 characters", max_length=400, ) image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", ) start_date = DateField(default=datetime.date.today) end_date = DateField(blank=True, null=True) latitude = FloatField(blank=True, null=True) longitude = FloatField(blank=True, null=True) register_url = URLField("Register URL", blank=True, null=True) body = CustomStreamField( blank=True, null=True, help_text=( "Optional body content. Supports rich text, images, embed via URL, " "embed via HTML, and inline code snippets" ), ) venue_name = CharField(max_length=100, blank=True, default="") venue_url = URLField("Venue URL", max_length=100, blank=True, default="") address_line_1 = CharField(max_length=100, blank=True, default="") address_line_2 = CharField(max_length=100, blank=True, default="") address_line_3 = CharField(max_length=100, blank=True, default="") city = CharField(max_length=100, blank=True, default="") state = CharField("State/Province/Region", max_length=100, blank=True, default="") zip_code = CharField("Zip/Postal code", max_length=100, blank=True, default="") country = CountryField(blank=True, default="") agenda = StreamField( StreamBlock([("agenda_item", AgendaItemBlock())], required=False), blank=True, null=True, help_text="Optional list of agenda items for this event", ) speakers = StreamField( StreamBlock( [ ("speaker", PageChooserBlock(target_model="people.Person")), ("external_speaker", ExternalSpeakerBlock()), ], required=False, ), blank=True, null=True, help_text="Optional list of speakers for this event", ) # Card fields card_title = CharField("Title", max_length=140, blank=True, default="") card_description = TextField("Description", max_length=400, blank=True, default="") card_image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", verbose_name="Image", ) # Meta fields keywords = ClusterTaggableManager(through=EventTag, blank=True) # Content panels content_panels = BasePage.content_panels + [ FieldPanel("description"), MultiFieldPanel( [ImageChooserPanel("image")], heading="Image", help_text=( "Optional header image. If not specified a fallback will be used. " "This image is also shown when sharing this page via social media" ), ), MultiFieldPanel( [ FieldPanel("start_date"), FieldPanel("end_date"), FieldPanel("latitude"), FieldPanel("longitude"), FieldPanel("register_url"), ], heading="Event details", classname="collapsible", help_text=mark_safe( "Optional time and location information for this event. Latitude and " "longitude are used to show a map of the event’s location. For more " "information on finding these values for a given location, " "'<a href='https://support.google.com/maps/answer/18539'>" "see this article</a>" ), ), StreamFieldPanel("body"), MultiFieldPanel( [ FieldPanel("venue_name"), FieldPanel("venue_url"), FieldPanel("address_line_1"), FieldPanel("address_line_2"), FieldPanel("address_line_3"), FieldPanel("city"), FieldPanel("state"), FieldPanel("zip_code"), FieldPanel("country"), ], heading="Event address", classname="collapsible", help_text=( "Optional address fields. The city and country are also shown " "on event cards" ), ), StreamFieldPanel("agenda"), StreamFieldPanel("speakers"), ] # Card panels card_panels = [ FieldPanel("card_title"), FieldPanel("card_description"), ImageChooserPanel("card_image"), ] # Meta panels meta_panels = [ MultiFieldPanel( [InlinePanel("topics")], heading="Topics", help_text=( "These are the topic pages the event will appear on. The first topic " "in the list will be treated as the primary topic and will be shown " "in the page’s related content." ), ), MultiFieldPanel( [ FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("social_image"), FieldPanel("keywords"), ], heading="SEO", help_text=( "Optional fields to override the default title and description " "for SEO purposes" ), ), ] # Settings panels settings_panels = [FieldPanel("slug")] edit_handler = TabbedInterface( [ ObjectList(content_panels, heading="Content"), ObjectList(card_panels, heading="Card"), ObjectList(meta_panels, heading="Meta"), ObjectList(settings_panels, heading="Settings", classname="settings"), ] ) @property def is_upcoming(self): """Returns whether an event is in the future.""" return self.start_date >= datetime.date.today() @property def primary_topic(self): """Return the first (primary) topic specified for the event.""" article_topic = self.topics.first() return article_topic.topic if article_topic else None @property def month_group(self): return self.start_date.replace(day=1) @property def country_group(self): return ( {"slug": self.country.code.lower(), "title": self.country.name} if self.country else {"slug": ""} ) @property def event_dates(self): """Return a formatted string of the event start and end dates""" event_dates = self.start_date.strftime("%b %-d") if self.end_date and self.end_date != self.start_date: event_dates += " – " start_month = self.start_date.strftime("%m") if self.end_date.strftime("%m") == start_month: event_dates += self.end_date.strftime("%-d") else: event_dates += self.end_date.strftime("%b %-d") return event_dates @property def event_dates_full(self): """Return a formatted string of the event start and end dates, including the year""" return self.event_dates + self.start_date.strftime(", %Y") def has_speaker(self, person): for speaker in self.speakers: # pylint: disable=not-an-iterable if speaker.block_type == "speaker" and str(speaker.value) == str( person.title ): return True return False
def get_redirect_path_with_status(self, path, full_path=None, language=None, version_slug=None): # add extra fields with the ``path`` and ``full_path`` to perform a # filter at db level instead with Python queryset = self.annotate( path=Value( path, output_field=CharField(), ), full_path=Value( full_path, output_field=CharField(), ), from_url_length=ExpressionWrapper( Length('from_url'), output_field=IntegerField(), ), # 1-indexed from_url_without_rest=Substr( 'from_url', 1, F('from_url_length') - 5, # Strip "$rest" output_field=CharField(), ), # 1-indexed full_path_without_rest=Substr( 'full_path', 1, F('from_url_length') - 5, # Strip "$rest" output_field=CharField(), ), ) prefix = Q( redirect_type='prefix', path__startswith=F('from_url'), ) page = Q( redirect_type='page', path__iexact=F('from_url'), ) exact = ( Q( redirect_type='exact', from_url__endswith='$rest', # This works around a bug in Django doing a substr and an endswith, # so instead we do 2 substrs and an exact # https://code.djangoproject.com/ticket/29155 full_path_without_rest=F('from_url_without_rest'), ) | Q( redirect_type='exact', full_path__iexact=F('from_url'), ) ) sphinx_html = ( Q( redirect_type='sphinx_html', path__endswith='/', ) | Q( redirect_type='sphinx_html', path__endswith='/index.html', ) ) sphinx_htmldir = Q( redirect_type='sphinx_htmldir', path__endswith='.html', ) # There should be one and only one redirect returned by this query. I # can't think in a case where there can be more at this point. I'm # leaving the loop just in case for now queryset = queryset.filter(prefix | page | exact | sphinx_html | sphinx_htmldir) for redirect in queryset.select_related('project'): new_path = redirect.get_redirect_path( path=path, language=language, version_slug=version_slug, ) if new_path: return new_path, redirect.http_status return (None, None)
class StrainSource(Model): """ A strain source contains all the meta-data relating to where a strain was collected from whom, by whom, and what ever else we can say about the strain. """ name = CharField(max_length=128, db_index=True, unique=True) old_id = CharField(max_length=50, db_index=True, null=True, blank=True) cluster = CharField(max_length=15, null=True, blank=True) date = DateField(null=True, blank=True) country = ForeignKey(Country, null=True, blank=True, related_name='sources') city = ForeignKey(Place, null=True, blank=True, related_name='sources') importer = ForeignKey(ImportSource, verbose_name='Import Source', null=True, blank=True) source_lab = CharField(max_length=100, verbose_name='Laboratory Source', db_index=True, null=True, blank=True) source_paper = ForeignKey(Paper, related_name="strains", null=True, blank=True) bioproject = ForeignKey(BioProject, related_name="strains", null=True, blank=True) patient_id = CharField(max_length=16, db_index=True) patient_age = PositiveIntegerField(null=True, blank=True) patient_sex = CharField(max_length=3, choices=SEXES, null=True, blank=True) patient_hiv = CharField(max_length=10, choices=HIVS, null=True, blank=True) spoligotype_type = IntegerField(null=True, blank=True) spoligotype_family = CharField("Spoligotype Family Parent Strain", max_length=255, null=True, blank=True) spoligotype_octal = CharField(validators=[is_octal], max_length=15, null=True, blank=True) lineage = ForeignKey(Lineage, related_name='strains', null=True, blank=True) rflp_type = CharField("Restriction fragment length polymorphism type", max_length=10, null=True, blank=True) rflp_family = CharField("Restriction fragment length polymorphism family", max_length=10, null=True, blank=True) insert_type = IntegerField("Insertion sequence 6110 type", null=True, blank=True) wgs_group = CharField("Whole Genome Sequence Group", max_length=10, null=True, blank=True) principle_group = IntegerField("Principle Generic Group", null=True, blank=True) resistance_group = CharField(max_length=4, choices=RESISTANCE_GROUP, null=True, blank=True) targeting = ForeignKey(TargetSet, null=True, blank=True) notes = TextField(null=True, blank=True) objects = StrainSourceManager() def natural_key(self): """Natural key for strains based just on their name""" return (self.name, ) def generate_resistance_group(self): """Generates the correct resistance_group based on drug information""" resistant_to = self.drugs.filter(resistance='r').values_list( 'drug__code', flat=True) sensitive_to = self.drugs.filter(resistance='s').values_list( 'drug__code', flat=True) group = None # Unknown if 'INH' in resistant_to and 'RIF' in resistant_to: group = 'MDR' if ('MOXI' in resistant_to) or ('GATI' in resistant_to) or ( 'LEVO' in resistant_to): if ('KAN' in resistant_to) or ('AMK' in resistant_to) or ( 'CAP' in resistant_to): group = 'XDR' elif resistant_to: group = 'ODR' elif 'INH' in sensitive_to or 'RIF' in sensitive_to: group = 's' self.resistance_group = group self.save() def __str__(self): return self.name or self.patient_id or ("Unnamed %d" % self.pk)
class BioProject(Model): name = CharField(max_length=128, unique=True) def __str__(self): return self.name
class CharSetModel(Model): field = SetCharField(base_field=CharField(max_length=8), size=3, max_length=32) field2 = SetCharField(base_field=CharField(max_length=8), max_length=255)
class VanillaAuthor(VanillaModel): name = CharField(max_length=32)
class CharListModel(Model): field = ListCharField(base_field=CharField(max_length=8), size=3, max_length=32)
class NameAuthor(Model): name = CharField(max_length=32, primary_key=True)
class BigCharListModel(Model): field = ListTextField(base_field=CharField(max_length=8))
class AuthorMultiIndex(Model): class Meta: index_together = ("name", "country") name = CharField(max_length=32, db_index=True) country = CharField(max_length=32)
class BaseSchema(Model): """ Metadata for an attribute. """ TYPE_TEXT = 'text' TYPE_FLOAT = 'float' TYPE_DATE = 'date' TYPE_BOOLEAN = 'bool' TYPE_ONE = 'one' TYPE_MANY = 'many' #TYPE_RANGE = 'range' #TYPE_OBJECT = 'object' DATATYPE_CHOICES = ( (TYPE_TEXT, _('text')), (TYPE_FLOAT, _('number')), (TYPE_DATE, _('date')), (TYPE_BOOLEAN, _('boolean')), (TYPE_ONE, _('choice')), (TYPE_MANY, _('multiple choices')), #(TYPE_RANGE, _('numeric range')), #(TYPE_OBJECT, _('django model')), ) title = CharField(_('title'), max_length=250, help_text=_('user-friendly attribute name')) name = AutoSlugField(_('name'), max_length=250, populate_from='title', editable=True, blank=True, slugify=slugify_attr_name) help_text = CharField(_('help text'), max_length=250, blank=True, help_text=_('short description for administrator')) datatype = CharField(_('data type'), max_length=6, choices=DATATYPE_CHOICES) required = BooleanField(_('required')) searched = BooleanField( _('include in search')) # i.e. full-text search? mb for text only filtered = BooleanField(_('include in filters')) sortable = BooleanField(_('allow sorting')) class Meta: abstract = True verbose_name, verbose_name_plural = _('schema'), _('schemata') ordering = ['title'] def __unicode__(self): return u'%s (%s)%s' % (self.title, self.get_datatype_display(), u' %s' % _('required') if self.required else '') def get_choices(self): """ Returns a queryset of choice objects bound to this schema. """ return self.choices.all() def get_attrs(self, entity): """ Returns available attributes for given entity instance. Handles many-to-one relations transparently. """ return self.attrs.filter(**get_entity_lookups(entity)) def save_attr(self, entity, value): """ Saves given EAV attribute with given value for given entity. If schema is not a choice, the value is saved to the corresponding Attr instance (which is created or updated). If schema is an cvhoice (one-to-one or many-to-one), the value is processed thusly: * if value is iterable, all Attr instances for corresponding managed choice schemata are updated (those with names from the value list are set to True, others to False). If a list item is not in available choices, ValueError is raised; * if the value is None, all corresponding Attr instances are reset to False; * if the value is neither a list nor None, it is wrapped into a list and processed as above (i.e. "foo" --> ["foo"]). """ if self.datatype in (self.TYPE_ONE, self.TYPE_MANY): self._save_choice_attr(entity, value) else: self._save_single_attr(entity, value) def _save_single_attr(self, entity, value=None, schema=None, create_nulls=False, extra={}): """ Creates or updates an EAV attribute for given entity with given value. :param schema: schema for attribute. Default it current schema instance. :param create_nulls: boolean: if True, even attributes with value=None are created (by default they are skipped). :param extra: dict: additional data for Attr instance (e.g. title). """ # If schema is not many-to-one, the value is saved to the corresponding # Attr instance (which is created or updated). schema = schema or self lookups = dict(get_entity_lookups(entity), schema=schema, **extra) try: attr = self.attrs.get(**lookups) except self.attrs.model.DoesNotExist: attr = self.attrs.model(**lookups) if create_nulls or value != attr.value: attr.value = value for k, v in extra.items(): setattr(attr, k, v) attr.save() def _save_choice_attr(self, entity, value): """ Creates or updates BaseChoice(s) attribute(s) for given entity. """ # value can be None to reset choices from schema if value == None: value = [] if not hasattr(value, '__iter__'): value = [value] if self.datatype == self.TYPE_ONE and len(value) > 1: raise TypeError('Cannot assign multiple values "%s" to TYPE_ONE ' 'must be only one BaseChoice instance.' % value) if not all(isinstance(x, BaseChoice) for x in value): raise TypeError('Cannot assign "%s": "Attr.choice" ' 'must be a BaseChoice instance.' % value) # drop all attributes for this entity/schema pair self.get_attrs(entity).delete() # Attr instances for corresponding managed choice schemata are updated for choice in value: self._save_single_attr(entity, schema=self, create_nulls=True, extra={'choice': choice})
class AuthorHugeName(Model): class Meta: db_table = "this_is_an_author_with_an_incredibly_long_table_name_" "you_know_it" name = CharField(max_length=32)
class Lawn(ExportModelOperationsMixin('lawn'), Model): location = CharField(max_length=100)
class Customer(Model): name = CharField(max_length=32)
class Genre(Model): genre = CharField(max_length=256) def __str__(self): return self.genre
class TitledAgedCustomer(AgedCustomer): class Meta: db_table = "titled aged customer" # Test name quoting title = CharField(max_length=16)
class CompositionPage(Page): composition_title = RichTextField(features=['bold', 'italic']) description = StreamField([('rich_text', RichTextBlock()), ('image', ImageChooserBlock())], blank=True) location = RichTextField( blank=True, features=['bold', 'italic', 'link', 'document-link'], ) genre = ParentalManyToManyField(Genre, blank=True, related_name='compositions') instrumentation = ParentalManyToManyField( 'Instrument', blank=True, ) orchestration = RichTextField( blank=True, features=['bold', 'italic'], help_text=( 'If the composition is for an ensemble, use this field to enter ' 'the orchestration of the work.')) duration = DurationField(null=True, blank=True, help_text='Expects data in the format "HH:MM:SS"') dedicatee = RichTextField( blank=True, features=['bold', 'italic', 'link', 'document-link'], ) text_source = RichTextField( blank=True, features=['bold', 'italic', 'link', 'document-link'], help_text='The source of the text used in the compostition.') collaborator = RichTextField( blank=True, features=['bold', 'italic', 'link', 'document-link'], help_text='Others that Decruck collaborated with.') manuscript_status = RichTextField( blank=True, features=['bold', 'italic', 'link', 'document-link'], help_text='Notes about the location and condition of the manuscript.') recording = StreamField([('rich_text', RichTextBlock()), ('image', ImageChooserBlock())], blank=True) information_up_to_date = BooleanField(default=False) scanned = BooleanField(default=False) premiere = RichTextField( blank=True, features=['bold', 'italic', 'link', 'document-link'], ) # For preview score preview_score = FileField( upload_to='composition_preview_scores/', blank=True, null=True, validators=[FileExtensionValidator(allowed_extensions=['pdf'])]) preview_score_checksum = CharField(editable=False, max_length=256, blank=True) preview_score_checked = False preview_score_updated = False # Extended Date Time Format nat_lang_edtf_string = CharField( verbose_name='Natural Language Date', help_text=('The EDTF date in natural language. This field is help ' 'users who aren\'t familiar with the EDTF. It does not ' 'change how the date is represented.'), blank=True, max_length=256) edtf_string = CharField( verbose_name='EDTF Date', help_text=mark_safe( 'A date in the <a href="https://www.loc.gov/standards/datetime/" ' 'target="_blank"><strong>Extended Date Time Format</strong></a>'), blank=True, max_length=256) lower_fuzzy = DateField(editable=False, null=True, blank=True) upper_fuzzy = DateField(editable=False, null=True, blank=True) lower_strict = DateField(editable=False, null=True, blank=True) upper_strict = DateField(editable=False, null=True, blank=True) nat_lang_year = CharField(editable=False, max_length=9, blank=True) def instrumentation_list(self): return ', '.join([str(i) for i in self.instrumentation.all()]) class Meta: verbose_name = "Composition" def get_context(self, request, *args, **kwargs): ctx = super().get_context(request, *args, **kwargs) try: search_idx = request.session['comp_search_index'] if search_idx: idx = search_idx.index(self.pk) prev_url = None next_url = None if idx > 0: pk = search_idx[idx - 1] prev_url = CompositionPage.objects.get(pk=pk).url if idx < len(search_idx) - 1: pk = search_idx[idx + 1] next_url = CompositionPage.objects.get(pk=pk).url ctx['prev_url'] = prev_url ctx['next_url'] = next_url ctx['comp_search_qs'] = request.\ session.get('comp_search_qs', '') except (KeyError, ValueError): pass return ctx def clean(self): super().clean() # Per Django docs: validate and modify values in Model.clean() # https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.clean # Check that nat_lang_edtf_string and edtf_string are either both set, or both unset if (self.nat_lang_edtf_string and not self.edtf_string) or (not self.nat_lang_edtf_string and self.edtf_string): raise ValidationError( 'If setting a date on a composition, an EDTF string and a natural language EDTF string must be provided.' ) # Validate edtf_string if self.edtf_string and self.nat_lang_edtf_string: try: e = parse_edtf(self.edtf_string) except EDTFParseException: raise ValidationError({ 'edtf_string': '{} is not a valid EDTF string'.format(self.edtf_string) }) self.lower_fuzzy = struct_time_to_date(e.lower_fuzzy()) self.upper_fuzzy = struct_time_to_date(e.upper_fuzzy()) self.lower_strict = struct_time_to_date(e.lower_strict()) self.upper_strict = struct_time_to_date(e.upper_strict()) if self.lower_strict.year != self.upper_strict.year: self.nat_lang_year = '{}-{}'.format(self.lower_strict.year, self.upper_strict.year) else: self.nat_lang_year = str(self.lower_strict.year) def save(self, *args, **kwargs): # If there's no preview score file, then just save the model if not self.preview_score: return super().save(*args, **kwargs) if self.preview_score_checked: # This was the cause of a subtle bug. Because this method can be # called multiple times during model creation, leaving this flag # set would cause the post save hook to fire multiple times. self.preview_score_updated = False return super().save(*args, **kwargs) h = hashlib.md5() for chunk in iter(lambda: self.preview_score.read(8192), b''): h.update(chunk) self.preview_score.seek(0) checksum = h.hexdigest() if not self.preview_score_checksum == checksum: self.preview_score_checksum = checksum self.preview_score_updated = True self.preview_score_checked = True return super().save(*args, **kwargs) content_panels = Page.content_panels + [ FieldPanel('composition_title'), StreamFieldPanel('description'), MultiFieldPanel( [FieldPanel('edtf_string'), FieldPanel('nat_lang_edtf_string')], help_text='Enter a date in the LOC Extended Date Time Format', heading='Date'), FieldPanel('location'), FieldPanel('instrumentation'), FieldPanel('orchestration'), FieldPanel('duration'), FieldPanel('dedicatee'), FieldPanel('premiere'), FieldPanel('genre'), FieldPanel('text_source'), FieldPanel('collaborator'), FieldPanel('manuscript_status'), FieldPanel('information_up_to_date'), FieldPanel('scanned'), FieldPanel('preview_score'), StreamFieldPanel('recording'), ] search_fields = Page.search_fields + [ index.SearchField('description', partial_match=True), index.SearchField('location', partial_match=True), index.SearchField('dedicatee', partial_match=True), index.SearchField('premiere', partial_match=True), index.SearchField('text_source', partial_match=True), index.SearchField('collaborator', partial_match=True), index.SearchField('manuscript_status', partial_match=True), index.SearchField('recording', partial_match=True), index.RelatedFields('genre', [ index.SearchField('genre_en', partial_match=True), index.SearchField('genre_fr', partial_match=True), ]), index.RelatedFields('instrumentation', [ index.SearchField('instrument_en', partial_match=True), index.SearchField('instrument_fr', partial_match=True), ]), ] parent_page_types = ['CompositionListingPage']
class Poll(VanillaModel): question = CharField(max_length=200) answer = CharField(max_length=200) pub_date = DateTimeField("date published", default=expensive_calculation)
class ScorePage(RoutablePageMixin, Page): cover_image = ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=PROTECT, related_name='cover_image') description = StreamField([('rich_text', RichTextBlock()), ('image', ImageChooserBlock())]) duration = DurationField(null=True, blank=True, help_text='Expects data in the format "HH:MM:SS"') file = FileField( upload_to='scores/', validators=[FileExtensionValidator(allowed_extensions=['pdf', 'zip'])]) preview_score = FileField( upload_to='preview_scores/', validators=[FileExtensionValidator(allowed_extensions=['pdf'])]) preview_score_checksum = CharField(editable=False, max_length=256, blank=True) preview_score_checked = False preview_score_updated = False genre = ParentalManyToManyField(Genre, blank=True, related_name='scores') date = CharField(max_length=256, blank=True) instrumentation = ParentalManyToManyField( 'Instrument', blank=True, help_text='The instrumentation of the compostition.') price = DecimalField(max_digits=6, decimal_places=2) materials = RichTextField( blank=True, features=['bold', 'italic', 'link', 'document-link'], help_text='The materials sent in the PDF file.') def save(self, *args, **kwargs): if self.preview_score_checked: # This was the cause of a subtle bug. Because this method can be # called multiple times during model creation, leaving this flag # set would cause the post save hook to fire multiple times. self.preview_score_updated = False return super().save(*args, **kwargs) h = hashlib.md5() for chunk in iter(lambda: self.preview_score.read(8192), b''): h.update(chunk) self.preview_score.seek(0) checksum = h.hexdigest() if not self.preview_score_checksum == checksum: self.preview_score_checksum = checksum self.preview_score_updated = True self.preview_score_checked = True return super().save(*args, **kwargs) @route(r'^([\w-]+)/$') def get_score_file(self, request, score_link_slug): if request.method == 'GET': item_link = get_object_or_404(OrderItemLink, slug=score_link_slug) if item_link.is_expired(): raise Http404() item_link.access_ip = request.META.get('REMOTE_ADDR', '0.0.0.0') item_link.save() return render(request, "main/score_page_download.html", { 'self': self, 'page': self, }) else: raise Http404() @route(r'^$') def score(self, request): cart_page = ShoppingCartPage.objects.first() if request.method == 'POST': in_cart = toggle_score_in_cart(request, self.pk) return render( request, "main/score_page.html", { 'self': self, 'page': self, 'in_cart': in_cart, 'cart_page': cart_page }) else: return render( request, "main/score_page.html", { 'self': self, 'page': self, 'in_cart': score_in_cart(request, self.pk), 'cart_page': cart_page }) class Meta: verbose_name = "Score Page" content_panels = Page.content_panels + [ FieldPanel('date'), FieldPanel('duration'), FieldPanel('genre'), FieldPanel('instrumentation'), FieldPanel('price'), StreamFieldPanel('description'), FieldPanel('materials'), FieldPanel('file'), FieldPanel('preview_score'), ImageChooserPanel('cover_image') ]
class User(AbstractUser): name = CharField(_("Name of User"), blank=True, null=True, max_length=255) def get_absolute_url(self): return reverse("users:detail", kwargs={"username": self.username})