class PostWithEmbeddedObject(BasicPost): '''Post that can display embedded objects, e.g. Youtube. ''' detail_post_embedded_html = EnhancedTextField(blank=True, default="\H") list_post_embedded_html = EnhancedTextField(blank=True, default="\H") class Meta: verbose_name = _('post with embedded object') verbose_name_plural = _('posts with embedded objects')
class Catalogue(models.Model): title = models.CharField(max_length=200) description = EnhancedTextField(blank=True, default="\W") documents = models.ManyToManyField(Document, blank=True, null=True, help_text=_('Documents in this archive.')) tags = generic.GenericRelation(TaggedItem, verbose_name=_('tags'), blank=True, null=True) copyright = models.ForeignKey(Copyright, blank=True, null=True) last_modified = models.DateTimeField(auto_now=True, editable=False) date_added = models.DateTimeField(auto_now_add=True, editable=False) def describe(self): return self.description @models.permalink def get_absolute_url(self): return ('calalogue_view', [str(self.id)]) def __unicode__(self): return self.title class Meta: verbose_name = _('catalogue') verbose_name_plural = _('catalogues') ordering = ['title',]
class Drug(models.Model): name = models.CharField(max_length=200, unique=True) synopsis = EnhancedTextField(blank=True, default="\W") formula = models.CharField(max_length=200, blank=True) chemical_photo = models.ForeignKey(Image, blank=True, null=True) studies = models.ManyToManyField(Study) side_effects = models.ManyToManyField(SideEffect)
class Condition(models.Model): name = models.CharField(max_length=200) description = EnhancedTextField(blank=True, default="\W") def __unicode__(self): return self.name class Meta: ordering = [ 'name', ]
class Category(models.Model): '''Represents a category for other objects. Designed with posts in mind. ''' name = models.CharField(max_length=50) description = EnhancedTextField(blank=True, default="\W") def __unicode__(self): return self.name class Meta: verbose_name = _('category') verbose_name_plural = _('categories')
class Gallery(models.Model): title = models.CharField(max_length=200) description = EnhancedTextField(blank=True, default="\W") tags = generic.GenericRelation(TaggedItem, verbose_name=_('tags'), blank=True, null=True) images = models.ManyToManyField(Image, blank=True, null=True, through="OrderedImage") copyright = models.ForeignKey(Copyright, blank=True, null=True) last_modified = models.DateTimeField(auto_now=True, editable=False) date_added = models.DateTimeField(auto_now_add=True, editable=False) def describe(self): return self.description def get_images(self): return self.images.order_by('orderedimage__position') def __unicode__(self): return self.title class Meta: verbose_name = _('gallery') verbose_name_plural = _('galleries') ordering = ['-last_modified',]
class Webpage(models.Model): """Represents manually maintained links to external web pages for display, say, on the front page of a website. """ title = models.CharField(max_length=200) url = models.CharField(max_length=200, verbose_name=_('URL')) byline = models.CharField(blank=True, max_length=200, help_text=_( 'The institution or organisation ' 'that produces this website. There is no ' 'problem with leaving this blank.')) date = models.DateField(blank=True, null=True, help_text=_( 'Sometimes it is useful to include the ' 'date a blog was written. But mostly this ' 'field will be left blank.')) html_A_tag_options = models.CharField( max_length=200, blank=True, help_text=_('You can put link, title and other ' 'HTML A tag attributes here. ' 'Leave blank if you are unsure.')) description = EnhancedTextField(blank=True, default="\W") date_last_edited = models.DateTimeField(auto_now=True, editable=False) def __unicode__(self): return self.title class Meta: ordering = [ 'date_last_edited', ] verbose_name = _('webpage') verbose_name_plural = _('webpages')
class Document(models.Model): title = models.CharField(max_length=200) file = FileBrowseField(max_length=200, directory="documents/", blank=True, null=True) url = models.URLField(blank=True, verify_exists=False, verbose_name='External URL', help_text= _('Use this field as an alternative to uploading a file')) content = EnhancedTextField(blank=True, help_text = _('Use this field as an alternative to uploading a file or specifying a URL.'), default=("\W")) description = EnhancedTextField(blank=True, help_text = _('Describe the document.'), default=("\W")) source = models.CharField(max_length=200, blank=True) publisher = models.CharField(max_length=200, blank=True) year_published = models.PositiveSmallIntegerField(blank=True, null=True) month_published = models.CharField(blank=True, max_length=2, choices=MONTH_CHOICES) day_published = models.PositiveSmallIntegerField(blank=True, null=True) recommended_citation = models.CharField(max_length=300, blank=True, help_text=_("Leave blank for system to automatically generate a citation.")) citation_format = models.CharField(max_length=3, choices=CITATION_FORMATS, default='DEF') pmid = models.IntegerField(blank=True, null=True, help_text=_("Enter a Public Library of Medicine identifier if there is one.")) doi = models.CharField(max_length=50, blank=True, help_text=_("Enter a Digital Object Identifier if there is one. E.g. 10.1021/ac0354342")) credits = generic.GenericRelation(OrderedCredit, verbose_name=_('credit',), blank=True, null=True) copyright = models.ForeignKey(Copyright, blank=True, null=True) tags = generic.GenericRelation(TaggedItem, verbose_name=_('tags'), blank=True, null=True) last_modified = models.DateTimeField(auto_now=True, editable=False) date_added = models.DateTimeField(auto_now_add=True, editable=False) def describe(self): return self.description def get_count_authors(self): return credit_length(self.credits) def get_authors(self): return credit_list(self.credits) def get_date_published(self): if not self.year_published: return "" date_published= unicode(abs(self.year_published)) if self.month_published: date_published += u' ' + self.get_month_published_display() if self.day_published: date_published += u' ' + unicode(self.day_published) return date_published def get_citation(self): """Generates a citation for the document. Currently only default citation is implemented. """ if self.recommended_citation: return self.recommended_citation citation = "" if self.citation_format == 'DEF': # Authors, title, date, source, PMID, DOI, URL authors = self.get_authors() if authors: citation += self.get_authors() + u'. ' citation += self.title + u'. ' date_published = self.get_date_published() if date_published: citation += date_published + u'. ' if self.source: citation += self.source + u'. ' if self.publisher: citation += self.publisher + u'. ' if self.pmid: citation += 'PMID: ' + unicode(self.pmid) + '. ' if self.doi: citation += 'DOI:' + self.doi + '. ' if self.url: citation += self.url + u'. ' elif self.file: citation += "http://" + unicode(Site.objects.get_current()) + unicode(self.file) else: citation += "http://" + unicode(Site.objects.get_current()) + self.get_absolute_url() return citation def clean(self): '''Validates the year/month/day combination is valid. Users can omit all three, but if they enter a month, they must enter a year and if they enter a day, they must enter a month. ''' # Don't allow draft entries to have a pub_date. if self.year_published: year = self.year_published if self.month_published: month = int(self.month_published) elif self.day_published: raise ValidationError(_('You must enter a month if you enter a day')) else: month = 1 # For purposes of validating the year if self.day_published: day = self.day_published else: day = 1 # For purposes of validating the year/month combination try: datetime.date(year,month,day) except (TypeError, ValueError): raise ValidationError(_('The date is invalid')) elif self.month_published or self.day_published: raise ValidationError(_('You must enter a year if you enter a month or day')) @models.permalink def get_absolute_url(self): return ('document-detail', [str(self.id)]) def __unicode__(self): return self.title class Meta: verbose_name = _('document') verbose_name_plural = _('documents') ordering = ['-year_published','-month_published', '-day_published']
class BasicPost(models.Model): '''This is the standard post. Complex post types requiring more sophisticated content should inherit from this one. This is really the most important class in the system. Most of the system's functionality is built around this model. ''' title = models.CharField(max_length=200, help_text=_( "The article title should be short, no more " "than seven words. It should convey the " "article's essence.")) subtitle = models.CharField( max_length=200, blank=True, help_text=_("The subtitles should also be short but it can " "be a bit longer than the title. " "Only use a subtitle if it adds useful " "information about the content or will draw " "readers to the article. Otherwise leave " "blank.")) authors = generic.GenericRelation(OrderedCredit, verbose_name=_('authors'), blank=True, null=True) teaser = EnhancedTextField(blank=True, help_text=_('For display on multi-post pages.'), default=("\W")) introduction = EnhancedTextField( blank=True, help_text=_('Displayed on detail post page separately from the body'), default=("\W")) body = EnhancedTextField( blank=True, default=("\W"), help_text=_('This is the content of the article.<br>' 'Note: Instead of filling in the <em>teaser</em> ' 'and <em>introduction</em> fields, you can use ' '<!--endteaser--> and/or ' '<!--endintro--> in this field to indicate ' 'where the teaser and/or introduction end ' 'respectively.')) pullout_text = models.CharField( max_length=400, blank=True, help_text=_('Usually used for a nice quote that will ' 'be prominently displayed')) slug = models.SlugField( help_text=_('Used in the URL to identify the post. ' 'This field is usually filled in ' 'automatically by the system. The ' 'system will ensure it has a unique ' 'value.<br/>' '<b>Warning:</b> If you change the ' 'slug after a post is published ' 'the link to the post will change ' 'and people who have bookmarked the ' 'old link will not find it.')) homepage = models.BooleanField( default=True, help_text=_('Check to display this post on the home page')) sticky = models.BooleanField( default=False, help_text=_('Check to display at top of home page even when newer ' 'posts are published.')) category = models.ForeignKey( Category, blank=True, null=True, help_text=_('Assign this post to a category. ' 'Posts can only have one category, but multiple tags')) date_published = models.DateTimeField( blank=True, null=True, help_text=_('A post is published if the publication date has ' 'passed. Leave blank while this is a draft.<br/>' '<b>Warning:</b> If you change this ' 'date after a post is published ' 'the link to the post will change ' 'and people who have bookmarked the ' 'old link will not find it.')) last_modified = models.DateTimeField(auto_now=True, editable=False) date_added = models.DateTimeField(auto_now_add=True, editable=False) allow_comments = models.BooleanField(default=True) detail_post_template = models.CharField( max_length=200, blank=True, help_text=_('Use this field to indicate an alternate html template ' 'for detail post pages. It is safe to leave this blank.')) list_post_template = models.CharField( max_length=200, blank=True, help_text=_('Use this field to indicate an alternate html template ' 'for list post pages. It is safe to leave this blank.')) detail_post_css_classes = models.CharField( max_length=200, blank=True, help_text=_('Use this field to indicate additional css classes for ' 'detail post pages. Separate classes with a space. It is ' 'safe to leave this blank.')) list_post_css_classes = models.CharField( max_length=200, blank=True, help_text=_('Use this field to indicate additional css classes for ' 'list post pages. Separate classes with a space. ' 'It is safe to leave this blank.')) copyright = models.ForeignKey(Copyright, blank=True, null=True) sites = models.ManyToManyField(Site) tags = generic.GenericRelation(TaggedItem, verbose_name=_('tags'), blank=True, null=True) objects = PostManager() def __get_template__(self, template_name, list_or_detail): '''Determines the template name for rendering a post and returns it as a string. The default template name is the class name + list_or_detail + html extension. ''' if template_name: return template_name else: import os return os.path.join( self._meta.app_label, self.__class__.__name__.lower() + list_or_detail + '.html') def get_post_list_template(self): '''Determines the post list template name and returns it as a string. A list post template renders a bunch of posts on a webpage. ''' return self.__get_template__(self.list_post_template, '_list_snippet') def get_post_detail_template(self): '''Determines the post detail template name and returns it as a string. A detail post template renders one post on a webpage. ''' return self.__get_template__(self.detail_post_template, '_detail_snippet') def get_authors(self): '''Uses the credit_list utility function to generate a list of authors as a printable string. ''' return credit_list(self.authors) def get_teaser_intro_body(self): '''Calculates the teaser, intro and body for this post The following possibilities exist: Key - 0: Field not set t: teaser field set i: intro field set x: teaser tag set y: intro tag set 01. 0000: No fields set - return first paragraph as intro and teaser, remainder as body 02. t000: teaser field set - return teaser and intro as teaser, full body as body 03. ti00: Simplest case - teaser and intro fields set. Return full body as body 04. tix0: Both teaser field and tag set. Teaser field overrides teaser tag. 05. ti0y: Both intro field and intro tag set. Intro field overrides intro tag 06. 0i00: Intro field set. Teaser set to intro. Body to remainder. 07. 0ix0: Intro field and teaser tag set. (Madness!) Body set to remainder. 08. 0ixy: Same as above, but intro field overrides intro tag. 09. 00x0: Teaser tag test. Set intro to teaser and body to remainder. 10. 00xy: Teaser and intro tags set. Body to remainder 11. 000y: Intro tag set. Set teaser to intro and body to remainder. ''' # Simplest case - they've all been set by the user teaser = unicode(self.teaser) intro = unicode(self.introduction) body = unicode(self.body) if not teaser: # Next simplest case: They've all been set in the body using tags contents = body.partition('<!--endteaser-->') if contents[1]: # The <!--endteaser--> tag is used teaser = contents[0] body = contents[2] # Body comes from remainder of text if not intro: # Intro field not set contents = body.partition('<!--endintro-->') if contents[1]: # The <!--endintro--> tag has been used intro = contents[0] body = contents[2] # Body is remainder of text else: # <!--endintro--> tag not used, so set intro to teaser intro = teaser else: # <!--endteaser--> tag not used if intro: # intro field has been set teaser = intro else: # intro field has not been set - look for <!--endintro--> contents = body.partition('<!--endintro-->') if contents[1]: # <!--endintro--> tag used teaser = intro = contents[0] body = contents[2] # body is remainder of text else: # No intro or teaser field set and no tags - get 1st para contents = body.partition('</p>') if not contents[1]: # Maybe it's a capital P? contents = body.partition('</P>') if not contents[1]: # No paragraphs! teaser = intro = contents[0] body = "" else: teaser = intro = contents[0] + contents[1] body = contents[2] else: # The teaser exists if not intro: # But the intro doesn't contents = body.partition('<!--endintro-->') if contents[1]: # <!--endintro--> tag used intro = contents[0] body = contents[2] else: # <!--endintro--> tag not used intro = teaser body = contents[0] return (teaser, intro, body) def get_teaser(self): return self.get_teaser_intro_body()[0] def get_introduction(self): return self.get_teaser_intro_body()[1] def get_body(self): return self.get_teaser_intro_body()[2] def describe(self): '''Describe methods are used by several apps in the system to return a description of themselves. Some templates depend on this method existing to produce sensible output. ''' return self.get_introduction() def is_published(self): '''A post is published if the date-time is > date_published. This method together with the Object Manager's published() method violates DRY to some extent. It is critical that they stay logically in sync. ''' try: if datetime.datetime.now() >= self.date_published: return True else: return False except: return False is_published.short_description = _("published") is_published.boolean = True @staticmethod def get_subclasses(): '''Determines all the subclasses of BasicPost, even new user defined ones. ''' return [ rel for rel in BasicPost._meta.get_all_related_objects() if isinstance(rel.field, models.OneToOneField) and issubclass(rel.field.model, BasicPost) ] def get_class(self): '''Will return the type of self unless this is a BasicPost in which case it will try to see if there's a subclass and return that. If that fails. return BasicPost. ''' if isinstance(self, BasicPost): for cls in BasicPost.get_subclasses(): try: inst = getattr(self, cls.var_name) if inst: return type(inst) except ObjectDoesNotExist: pass return BasicPost else: return type(self) def get_class_name(self): return self.get_class().__name__ def describe_for_admin(self): '''Returns a string description of the type of this Post for the admin interface. ''' return self.get_class()._meta.verbose_name describe_for_admin.short_description = "Type" describe_for_admin.allow_tags = True @models.permalink def get_admin_url(self): '''Ensures that if the user clicks on a Post in the admin interface, the correct change screen is opened for this type of Post. For example, if the Post is a PostWithImage, then the PostWithImage admin change screen must open, not the BasicPost change screen. ''' cls = self.get_class() return ('admin:post_' + self.get_class().__name__.lower() + '_change', [str(self.pk)]) def render_admin_url(self): '''Called from the Admin interface to generates the html to link a post to its correct change screen. Works in conjunction with get_admin_url() ''' return u'<a href="'+ self.get_admin_url() + u'">'+ unicode(self.pk) + \ u'</a>' render_admin_url.short_description = _('ID') render_admin_url.allow_tags = True render_admin_url.admin_order_field = 'id' @staticmethod def get_posts_by_tags_union(tags): '''Returns all posts which contain any of the tags in the list of tags passed as an argument to this method. ''' if type(tags) == str or type(tags) == unicode: tags = tags.rsplit(",") if type(tags) != list: raise TypeError("Tags is a %s. Expected tags to be a list, string" " or unicode object." % unicode(type(tags))) posts = [] for t in tags: try: tag = Tag.objects.get(name=t) except Tag.DoesNotExist: continue posts_for_this_tag = list(TaggedItem.objects.\ get_by_model(BasicPost, tag)) for cls in BasicPost.get_subclasses(): posts_for_this_tag += list(TaggedItem.objects.\ get_by_model(cls.model, tag)) posts += filter(lambda p: p.is_published() and \ Site.objects.get_current() in p.sites.all(), posts_for_this_tag) return list(set(posts)) # Remove duplicates @staticmethod def get_posts_by_tags_intersection(tags): '''Returns all posts that have all the tags in the list of tags passed in the argument to this method. ''' if type(tags) == str or type(tags) == unicode: tags = tags.rsplit(",") if type(tags) != list: raise TypeError("Tags is a %s. Expected tags to be a list, string" " or unicode object." % unicode(type(tags))) posts = set([]) for i, t in enumerate(tags): try: tag = Tag.objects.get(name=t) except Tag.DoesNotExist: continue posts_for_this_tag = list(TaggedItem.objects.\ get_by_model(BasicPost, tag)) for cls in BasicPost.get_subclasses(): posts_for_this_tag += list(TaggedItem.objects.\ get_by_model(cls.model, tag)) posts_for_this_tag = set(filter(lambda p: p.is_published() and \ Site.objects.get_current() in \ p.sites.all(), posts_for_this_tag)) if i > 0: posts = posts & posts_for_this_tag else: posts = posts_for_this_tag return list(posts) def _get_unique_slug(self): '''Makes slug unique, if it is not already, and returns it as a string. ''' slug_unique = False counter = 1 slug = self.slug while not slug_unique: if self.pk: posts = BasicPost.objects.filter(slug=slug).\ exclude(pk=self.pk) else: posts = BasicPost.objects.filter(slug=slug) if len(posts) == 0: slug_unique = True else: slug = self.slug + "-" + unicode(counter) counter += 1 return slug def save(self, *args, **kwargs): # Make the slug unique self.slug = self._get_unique_slug() super(BasicPost, self).save(*args, **kwargs) @staticmethod def get_posts_by_categories(categories): '''Returns all posts which are in the given categories. Note category is a foreign key, so a post only belongs to one category. Therefore there is no union or intersection operation as there is for tags. ''' if type(categories) == str or type(categories) == unicode: categories = categories.rsplit(",") if type(categories) != list: raise TypeError("Categories is a %s. Expected tags to be a list, " "string or unicode object." % unicode(type(categories))) return BasicPost.objects.published().\ filter(category__name__in=categories).\ select_subclasses().distinct() @staticmethod def get_posts_by_author(author): '''Returns all posts which is authored or co-authored by the author passed as an argument to this method. ''' if type(author) == str or type(author) == unicode: author = int(author) if type(author) != int: raise TypeError( "Author is a %s. Expected author to be an int, string" " or unicode object." % unicode(type(author))) author = Credit.objects.get(id=author) ordered_credits = OrderedCredit.objects.filter(credit=author) posts = [] post_classes = [BasicPost] for subclass in BasicPost.get_subclasses(): post_classes.append(subclass.model) for ordered_credit in ordered_credits: if type(ordered_credit.content_object) in post_classes: if ordered_credit.content_object.is_published(): posts.append(ordered_credit.content_object) posts = set(posts) return posts @models.permalink def get_absolute_url(self): if self.is_published(): return ('post_detail', [ str(self.date_published.year), str(self.date_published.month), str(self.date_published.day), str(self.slug) ]) else: return ('post_draft_detail', [str(self.id)]) def __unicode__(self): return self.title class Meta: verbose_name = _('post') verbose_name_plural = _('posts') ordering = ['-sticky', '-date_published'] unique_together = ('slug', 'date_published')
class Story(models.Model): '''Story model encapsulates a collection of ordered posts. Useful for representing a book or connected articles that need a table of contents. ''' title = models.CharField(max_length=200) slug = models.SlugField() description = EnhancedTextField(blank=True, default="\W") posts = models.ManyToManyField(BasicPost, through='OrderedPost', blank=True, null=True) authors = generic.GenericRelation(OrderedCredit, verbose_name=_('authors'), blank=True, null=True) date_published = models.DateTimeField( blank=True, null=True, help_text=_('Leave blank while this is a draft.')) last_modified = models.DateTimeField(auto_now=True, editable=False) date_added = models.DateTimeField(auto_now_add=True, editable=False) tags = generic.GenericRelation(TaggedItem, verbose_name=_('tags'), blank=True, null=True) def is_published(self): try: if datetime.datetime.now() >= self.date_published: return True else: return False except: return False is_published.short_description = _("published") def get_posts(self): posts = [ orderedpost.post for orderedpost in self.orderedpost_set.all() if orderedpost.post.is_published() ] return posts def describe(self): return self.description @models.permalink def get_absolute_url(self): if self.is_published(): return ('story_detail', [ str(self.date_published.year), str(self.date_published.month), str(self.date_published.day), self.slug ]) else: return ( 'draft_story', [str(self.id)], ) def __unicode__(self): return self.title class Meta: verbose_name = _('story') verbose_name_plural = _('stories') ordering = ['-date_published']
class Image(models.Model): title = models.CharField(max_length=200) slug = models.SlugField(unique=True) file = FileBrowseField(max_length=200, directory="images/", format='image', blank=True, null=True) preferred_size = models.CharField(max_length=1, choices=SIZES, blank=True, help_text=_('In some cases setting this can help HTML ' 'writers display the image display.')) caption = models.CharField(max_length=200, blank=True) url = models.URLField(blank=True, verify_exists=False, verbose_name=('URL'), help_text=_('URL for image to link to, usually the source.')) description = EnhancedTextField(blank=True, default="\W") credits = generic.GenericRelation(OrderedCredit, verbose_name=_('Credit',), blank=True, null=True) copyright = models.ForeignKey(Copyright, blank=True, null=True) tags = generic.GenericRelation(TaggedItem, verbose_name=_('tags'), blank=True, null=True) last_modified = models.DateTimeField(auto_now=True, editable=False) date_added = models.DateTimeField(auto_now_add=True, editable=False) def image_thumbnail(self): if self.file and self.file.filetype == "Image": return '<img src="%s" />' % self.file.url_thumbnail else: return "" image_thumbnail.allow_tags = True image_thumbnail.short_description = "Thumbnail" def _get_unique_slug(self): '''Makes slug unique, if it is not already, and returns it as a string. ''' slug_unique = False counter = 1 slug = self.slug while not slug_unique: if self.id: images = Image.objects.filter(slug=slug).\ exclude(pk=self.id) else: images = Image.objects.filter(slug=slug) if len(images) == 0: slug_unique = True else: slug = self.slug + "-" + unicode(counter) counter += 1 return slug def get_credits(self): return credit_list(self.credits) def describe(self): if unicode(self.description).strip(): return self.description else: return self.caption @models.permalink def get_absolute_url(self): return ('image_view', [str(self.id)]) def __unicode__(self): return self.title class Meta: verbose_name = _('image') verbose_name_plural = _('images') ordering = ['-last_modified']