class TabCategory(Model): name = CharField(max_length=22) icon = ResizedImageField(_('Icon'), 32, 32, upload_to='shields/icons') class Meta: db_table = 'extra_tabcategory' def __unicode__(self): return self.name
class ReleaseStatus(Model): """For non-released (finalised) Releases, what stage are we at""" STYLES = (('blue', _('Blue')), ) name = CharField(max_length=32) desc = CharField(_('Description'), max_length=128) style = CharField(max_length=32, choices=STYLES, **null) icon = ResizedImageField(**upload_to('icons', 32, 32)) def __str__(self): return self.name
class Report(Model): """A project should always have at least one update with its primary description""" project = ForeignKey(Project, related_name='updates') description = TextField(_("Description"), validators=[MaxLengthValidator(12288)]) image = ResizedImageField(_("Image"), max_height=400, max_width=400, upload_to=os.path.join('project', 'update', '%Y'), **null) creator = ForeignKey(settings.AUTH_USER_MODEL, default=get_user) created = DateTimeField(auto_now_add=True, db_index=True) edited = DateTimeField(auto_now=True) def __str__(self): return self.description
class Project(Model): """A project details work that needs to be done""" DIFFICULTIES = ( (0, _('Unknown')), (1, _('Easy')), (2, _('Moderate')), (3, _('Hard')), (4, _('Very hard')), ) LOGO = os.path.join(settings.STATIC_URL, 'images', 'project_logo.png') BANNER = os.path.join(settings.STATIC_URL, 'images', 'project_banner.png') difficulty = IntegerField(_('Difficulty'), choices=DIFFICULTIES, default=2) title = CharField(_('Title'), max_length=100) pitch = CharField(_('Short Summary'), max_length=255, **null) slug = SlugField(unique=True) description = TextField(_('Description'), validators=[MaxLengthValidator(50192)], **null) banner = ResizedImageField(_("Banner (920x120)"), max_height=120, max_width=920, min_height=90, min_width=600, upload_to=os.path.join('project', 'banner'), default=BANNER) logo = ResizedImageField(_("Logo (150x150)"), max_height=150, max_width=150, min_height=150, min_width=150, upload_to=os.path.join('project', 'logo'), default=LOGO) duration = IntegerField(_('Expected Duration in Days'), default=0) started = DateTimeField(**null) finished = DateTimeField(**null) created = DateTimeField(auto_now_add=True, db_index=True) edited = DateTimeField(auto_now=True) proposer = ForeignKey(settings.AUTH_USER_MODEL, related_name='proposed_projects', default=get_user) manager = ForeignKey(settings.AUTH_USER_MODEL, related_name='manages_projects', **null) reviewer = ForeignKey(settings.AUTH_USER_MODEL, related_name='reviews_projects', **null) second = ForeignKey(settings.AUTH_USER_MODEL, related_name='seconds_projects', **null) project_type = ForeignKey(ProjectType) is_fundable = BooleanField(default=False) is_approved = BooleanField(_('Pre-approved'), default=False) criteria = ManyToManyField('Criteria', blank=True) def __str__(self): return self.title def save(self, **kwargs): if not self.slug and self.title: self.slug = slugify(self.title) Model.save(self, **kwargs) def progress(self): """Returns a float, percentage of completed deliverable items""" count = self.deliverables.all().count() if count: done = self.deliverables.filter(finished__isnull=False).count() if done > 0: return (done / float(count)) * 100.0 return self.finished and 100.0 or 0.0 def get_absolute_url(self): return reverse('project', kwargs={'slug': self.slug}) def get_status(self): """Returns a (preliminary) status number / string tuple for displaying in templates possible status include: proposed (needs review), application phase (free to take), in progress, finished. The number could be used for CSS classing.""" if self.manager is None: return (1, _("Proposed")) elif self.started is None: return (2, _("Application Phase")) elif self.started is not None: return (3, _("In Progress")) elif self.finished is not None: return (4, _("Completed")) else: return (0, _("Undetermined")) def get_expected_enddate(self): if self.started is not None: return self.started + datetime.timedelta(days=self.duration) else: return datetime.datetime.now() + datetime.timedelta( days=self.duration)
class Release(Model): """A release of inkscape""" parent = ForeignKey('self', related_name='children', **null) version = CharField(_('Version'), max_length=16, db_index=True, unique=True) codename = CharField(_('Codename'), max_length=32, db_index=True, **null) release_notes = TextField(_('Release notes'), **null) release_date = DateField(_('Release date'), db_index=True, **null) status = ForeignKey(ReleaseStatus, **null) edited = DateTimeField(_('Last edited'), auto_now=True) created = DateTimeField(_('Date created'), auto_now_add=True, db_index=True) background = ResizedImageField(**upload_to('background', 960, 360)) manager = ForeignKey( User, verbose_name=_("Manager"), related_name='releases', help_text=_("Looks after the release schedule and release meetings."), **null) reviewer = ForeignKey( User, verbose_name=_("Reviewer"), related_name='rev_releases', help_text=_("Reviewers help to make sure the release is working."), **null) bug_manager = ForeignKey( User, verbose_name=_("Bug Manager"), related_name='bug_releases', help_text=_("Manages critical bugs and decides what needs fixing."), **null) translation_manager = ForeignKey( User, verbose_name=_("Translation Manager"), related_name='tr_releases', help_text=_( "Translation managers look after all translations for the release." ), **null) objects = ReleaseQuerySet.as_manager() class Meta: ordering = '-release_date', get_latest_by = 'release_date' def __str__(self): if not self.codename: return "Inkscape %s" % self.version return "Inkscape %s (%s)" % (self.version, self.codename) def get_absolute_url(self): return reverse('releases:release', kwargs={'version': self.version}) def breadcrumb_parent(self): return self.parent if self.parent else Release.objects.all() def is_prerelease(self): """Returns True if this child release happened before parent release""" (par, dat) = (self.parent, self.release_date) return par and dat and (not par.release_date or par.release_date > dat) def get_notes(self): """Returns a translated release notes""" lang = get_language() if not lang or lang == DEFAULT_LANG: return self.release_notes try: return self.translations.get(language=lang).translated_notes except ReleaseTranslation.DoesNotExist: return self.release_notes @property def revisions(self): return Release.objects.filter(Q(parent_id=self.pk) | Q(id=self.pk)) @property def latest(self): return self.revisions.order_by('-release_date')[0] def responsible_people(self): """Quick list of all responsible people with labels""" for key in ('manager', 'reviewer', 'translation_manager', 'bug_manager'): yield (getattr(Release, key).field.verbose_name, getattr(Release, key).field.help_text, getattr(self, key))
class Platform(Model): """A list of all platforms we release to""" name = CharField(_('Name'), max_length=64) desc = CharField(_('Description'), max_length=255) parent = ForeignKey('self', related_name='children', verbose_name=_("Parent Platform"), **null) manager = ForeignKey(User, verbose_name=_("Platform Manager"), **null) codename = CharField(max_length=255, **null) order = PositiveIntegerField(default=0) match_family = CharField(max_length=32, db_index=True, help_text=_('User agent os match, whole string.'), **null) match_version = CharField( max_length=32, db_index=True, help_text= _('User agent os version partial match, e.g. |10|11| will match both version 10 and version 11, must have pipes at start and end of string.' ), **null) match_bits = PositiveIntegerField(db_index=True, choices=((32, '32bit'), (64, '64bit')), **null) icon = ResizedImageField(**upload_to('icons', 32, 32)) image = ResizedImageField(**upload_to('icons', 256, 256)) uuid = lambda self: slugify(self.name) tab_name = lambda self: self.name tab_text = lambda self: self.desc tab_cat = lambda self: {'icon': self.icon} root = lambda self: self.ancestors()[-1] depth = lambda self: len(self.ancestors) - 1 class Meta: ordering = '-order', 'codename' def save(self, **kwargs): codename = "/".join([slugify(anc.name) for anc in self.ancestors()][::-1]) if self.codename != codename: self.codename = codename if self.pk: for child in self.children.all(): child.save() return super(Platform, self).save(**kwargs) def get_manager(self): if self.manager: return self.manager if self.parent: return self.parent.get_manager() return None def ancestors(self, _to=None): _to = _to or [self] if self.parent and self.parent not in _to: # Prevent infinite loops getting parents _to.append(self.parent) self.parent.ancestors(_to) return _to def descendants(self, _from=None): _from = _from or [] for child in self.children.all(): if child in _from: # Prevent infinite loops getting children continue _from.append(child) child.descendants(_from) return _from def __str__(self): return self.codename.replace('/', ' : ').replace('_', ' ').title()
class Gallery(Model): GALLERY_STATUSES = ( (None, 'No Status'), (' ', 'Casual Wish'), ('1', 'Draft'), ('2', 'Proposal'), ('3', 'Reviewed Proposal'), ('+', 'Under Development'), ('=', 'Complete'), ('-', 'Rejected'), ) user = ForeignKey(settings.AUTH_USER_MODEL, related_name='galleries', default=get_user) group = ForeignKey(Group, related_name='galleries', **null) category = ForeignKey(Category, related_name='galleries', **null) name = CharField(max_length=64) slug = SlugField(max_length=70) desc = TextField(_('Description'), validators=[MaxLengthValidator(50192)], **null) thumbnail = ResizedImageField(_('Thumbnail'), 190, 190, **upto('thumb')) status = CharField(max_length=1, db_index=True, choices=GALLERY_STATUSES, **null) items = ManyToManyField(Resource, related_name='galleries', blank=True) contest_submit = DateField( help_text=_('Start a contest in this gallery on this date (UTC).'), **null) contest_voting = DateField( help_text=_('Finish the submissions and start voting (UTC).'), **null) contest_count = DateField( help_text=_('Voting is finished, but the votes are being counted.'), **null) contest_finish = DateField(help_text=_( 'Finish the contest, voting closed, winner announced (UTC).'), **null) _is_generic = lambda self, a, b: a and a <= now().date() < b is_contest = property(lambda self: bool(self.contest_submit)) is_pending = property(lambda self: self.contest_submit and self. contest_submit > now().date()) is_submitting = property(lambda self: self._is_generic( self.contest_submit, self.contest_voting)) is_voting = property(lambda self: self._is_generic(self.contest_voting, ( self.contest_count or self.contest_finish))) is_counting = property( lambda self: self._is_generic(self.contest_count, self.contest_finish)) is_finished = property(lambda self: self.contest_finish and self. contest_finish <= now().date()) objects = GalleryQuerySet.as_manager() def __unicode__(self): if self.category: return self.name elif self.group: return _(u"%(gallery_name)s (for group %(group_name)s)") \ % {'gallery_name': self.name, 'group_name': unicode(self.group)} return _(u"%(gallery_name)s (by %(user_name)s)") \ % {'gallery_name': self.name, 'user_name': unicode(self.user)} def __str__(self): return unicode(self).encode('utf8') def tag_cloud(self): """Returns a cloud collection""" return Tag.objects.filter( resources__galleries__id=self.pk).as_cloud('resources') @property def votes(self): """Returns a queryset of Votes for this category""" return Vote.objects.filter(resource__galleries=self.pk) def save(self, *args, **kwargs): set_slug(self) super(Gallery, self).save(*args, **kwargs) def get_absolute_url(self): if self.category: return reverse('resources', kwargs={ 'category': self.category.slug, 'galleries': self.slug, }) if self.group: try: return reverse('resources', kwargs={ 'team': self.group.team.slug, 'galleries': self.slug, }) except Team.DoesNotExist: pass elif self.slug: return reverse('resources', kwargs={ 'username': self.user.username, 'galleries': self.slug, }) return reverse('resources', kwargs={'gallery_id': self.pk}) @property def winners(self): """Return the resource with the most votes""" if self.is_contest and self.is_finished: if self.contest_count is None: item = self.items.latest('liked') item.extra_status = Resource.CONTEST_WINNER item.save() self.contest_count = self.contest_finish return self.items.filter(extra_status=Resource.CONTEST_WINNER) return None @property def value(self): return self.slug @property def parent(self): if self.category: return self.category return (self.group or self.user).galleries.all() def is_visible(self): return self.items.for_user(get_user()).count() or self.is_editable() def is_editable(self): user = get_user() return user and (not user.id is None) and ( self.user == user or user.is_superuser \ or (user.groups.count() and self.group in user.groups.all())) def thumbnail_url(self): if self.thumbnail: return self.thumbnail.url for item in self.items.all(): if item.is_visible(): return item.icon_url() return static('images', 'folder.svg') def __len__(self): return self.items.count()
class Resource(Model): """This is a resource with an uploaded file""" owner_field = 'user' is_resource = True ENDORSE_NONE = 0 ENDORSE_HASH = 1 ENDORSE_SIGN = 5 ENDORSE_AUTH = 10 CONTEST_WINNER = 1 CONTEST_RUNNER_UP = 2 CONTEST_CONSIDER = 3 EXTRA_CHOICES = ( (None, _('No extra status')), (CONTEST_WINNER, _('Winner')), (CONTEST_RUNNER_UP, _('Runner Up')), (CONTEST_CONSIDER, _('Next Round')), ) EXTRA_CSS = ['', 'ribbon winner', 'ribbon runnerup', 'ribbon consider'] user = ForeignKey(settings.AUTH_USER_MODEL, related_name='resources', default=get_user) name = CharField(max_length=64) slug = SlugField(max_length=70) desc = TextField(_('Description'), validators=[MaxLengthValidator(50192)], **null) category = ForeignKey(Category, verbose_name=_("Category"), related_name='items', **null) tags = ManyToManyField(Tag, verbose_name=_("Tags"), related_name='resources', blank=True) created = DateTimeField(**null) edited = DateTimeField(**null) # End of copyright, last file-edit/updated. published = BooleanField(default=False) thumbnail = ResizedImageField(_('Thumbnail'), 190, 190, **upto('thumb')) rendering = ResizedImageField(_('Rendering'), 780, 600, **upto('render')) link = URLField(_('External Link'), **null) liked = PositiveIntegerField(default=0) viewed = PositiveIntegerField(default=0) downed = PositiveIntegerField(_('Downloaded'), default=0) fullview = PositiveIntegerField(_('Full Views'), default=0) media_type = CharField(_('File Type'), max_length=128, **null) media_x = IntegerField(**null) media_y = IntegerField(**null) extra_status = PositiveSmallIntegerField(choices=EXTRA_CHOICES, **null) extra_css = property(lambda self: self.EXTRA_CSS[self.extra_status]) # ======== ITEMS FROM RESOURCEFILE =========== # download = FileField(_('Consumable File'), storage=resource_storage, **upto('file', blank=True)) license = ForeignKey(License, verbose_name=_("License"), on_delete=SET_NULL, **null) owner = BooleanField(_('Permission'), choices=OWNS, default=True) owner_name = CharField(_('Owner\'s Name'), max_length=128, **null) signature = FileField(_('Signature/Checksum'), **upto('sigs')) verified = BooleanField(default=False) mirror = BooleanField(default=False) embed = BooleanField(default=False) checked_by = ForeignKey(settings.AUTH_USER_MODEL, related_name='resource_checks', **null) checked_sig = FileField(_('Counter Signature'), **upto('sigs')) objects = ResourceManager() class Meta: get_latest_by = 'created' def __unicode__(self): return self.name def __str__(self): return self.name.encode('utf8') def summary_string(self): return _("%(file_title)s by %(file_author)s (%(years)s)") \ % {'file_title': self.name, 'file_author': self.user, 'years': self.years} @classmethod def from_db(cls, db, field_names, values): db_instance = super(Resource, cls).from_db(db, field_names, values) # cache old value for link db_instance._old_link = values[field_names.index('link')] return db_instance @property def parent(self): if self.is_pasted: cat = self.category cat._parent = self.user.resources.all() return cat galleries = self.galleries.all() if galleries: return galleries[0] return self.user.resources.all() def description(self): if not self.desc: return '-' if '[[...]]' in self.desc: return self.desc.split('[[...]]')[0] return self.desc[:1000] def read_more(self): if not self.desc: return False return len(self.desc) > 1000 or '[[...]]' in self.desc def save(self, **kwargs): if self.download and not self.download._committed: # There is a download file and it has been changed if self.pk: # Save the old download file in a revision ResourceRevision.from_resource(self) self.verified = False self.edited = now() if hasattr(self, '_mime'): delattr(self, '_mime') try: self.media_type = str(self.file.mime) (self.media_x, self.media_y) = self.file.media_coords except ValueError: # Text file is corrupt, treat it as a binary self.media_type = 'application/octet-stream' # the signature on an existing resource has changed elif self.signature and not self.signature._committed: self.verified = False # mark as edited for link-only resources when they are added # or when link changes if not self.download and ((self._state.adding and self.link) or \ (not self._state.adding and hasattr(self, '_old_link') and self._old_link != self.link)): self.edited = now() signal = False if not self.created and self.published: self.created = now() signal = True set_slug(self) ret = super(Resource, self).save(**kwargs) if signal: from .alert import post_publish post_publish.send(sender=Resource, instance=self) return ret def filename(self): return os.path.basename(self.download.name) def rendering_name(self): return os.path.basename(self.rendering.name) @property def file(self): if not hasattr(self, '_fileEx'): mime = MimeType(filename=self.download.path) self._fileEx = FileEx(self.download.file, mime) return self._fileEx def signature_type(self): return self.signature.name.rsplit('.', 1)[-1] def _verify(self, _type): if _type == 'sig': # GPG Signature return gpg_verify(self.user, self.signature, self.download) return hash_verify(_type, self.signature, self.download) def endorsement(self): if not self.signature: return self.ENDORSE_NONE sig_type = self.signature_type() if not self.verified: self.verified = self._verify(sig_type) self.save() if self.verified and sig_type == 'sig': if self.user.has_perm('resource.change_resourcemirror'): return self.ENDORSE_AUTH return self.ENDORSE_SIGN return self.verified and self.ENDORSE_HASH or self.ENDORSE_NONE def rendering_url(self): if self.rendering: return self.rendering.url if self.download and self.mime().is_image(): return self.download.url if self.thumbnail and os.path.exists(self.thumbnail.path): return self.thumbnail.url return self.icon_url() def thumbnail_url(self): """Returns a 190px thumbnail either from the thumbnail, the image itself or the mimetype icon""" if self.thumbnail and os.path.exists(self.thumbnail.path): return self.thumbnail.url if self.rendering and os.path.exists(self.rendering.path): return self.rendering.url if self.download and self.mime().is_image() \ and os.path.exists(self.download.path) \ and self.download.size < settings.MAX_PREVIEW_SIZE: return self.download.url return self.icon_url() def icon_url(self): if not self.download: icon = "broken" if self.link: icon = "video" if self.is_video else "link" return self.mime().static(icon) return self.mime().icon() def as_lines(self): """Returns the contents as text""" return syntaxer(self.as_text(), self.mime()) def as_line_preview(self): """Returns a few lines of text""" return syntaxer(self.as_text(20), self.mime()) def as_text(self, lines=None): return self.file.as_text(lines=lines) @property def is_pasted(self): # Using pk is 1 is NOT idea, XXX find a better way. return self.category_id and self.category_id == 1 def get_absolute_url(self): if self.is_pasted: return reverse('pasted_item', args=[str(self.pk)]) if self.slug: return reverse('resource', kwargs={ 'username': self.user.username, 'slug': self.slug }) return reverse('resource', kwargs={'pk': self.pk}) @property def years(self): if self.created and self.edited \ and self.created.year != self.edited.year: return "%d-%d" % (self.created.year, self.edited.year) if self.edited: return str(self.edited.year) if self.created is None: self.created = now() self.save() return str(self.created.year) def is_visible(self): return get_user( ).pk == self.user_id or self.published and self.is_available() def is_available(self): return not self.download or os.path.exists(self.download.path) def voted(self): return self.votes.filter(voter_id=get_user().pk).first() @property def is_new(self): return not self.category @property def is_video(self): return bool(self.video) @property def video(self): return video_embed(self.link) @property def next(self): """Get the next item in the gallery which needs information""" return Resource.objects.filter(category__isnull=True, user_id=self.user.pk)\ .exclude(pk=self.pk).latest('created') @property def gallery(self): try: return self.galleries.all()[0] except IndexError: return None @cached def mime(self): """Returns an encapsulated media_type as a MimeType object""" return MimeType(self.media_type or 'application/unknown') def link_from(self): """Returns the domain name or useful name if known for link""" try: domain = '.'.join(self.link.split("/")[2].split('.')[-2:]) return DOMAINS.get(domain, domain) except Exception: return 'unknown'
class User(AbstractUser): bio = TextField(_('Bio'), validators=[MaxLengthValidator(4096)], **null) photo = ResizedImageField(_('Photograph (square)'), null=True, blank=True, upload_to='photos', max_width=190, max_height=190) language = CharField(_('Default Language'), max_length=8, choices=settings.LANGUAGES, **null) ircnick = CharField(_('IRC Nickname'), max_length=20, **null) ircpass = CharField(_('Freenode Password (optional)'), max_length=128, **null) dauser = CharField(_('deviantArt User'), max_length=64, **null) ocuser = CharField(_('Openclipart User'), max_length=64, **null) tbruser = CharField(_('Tumblr User'), max_length=64, **null) gpg_key = TextField(_('GPG Public Key'), help_text=_('<strong>Signing and Checksums for Uploads</strong><br/> ' 'Either fill in a valid GPG key, so you can sign your uploads, ' 'or just enter any text to activate the upload validation feature ' 'which verifies your uploads by comparing checksums.<br/>' '<strong>Usage in file upload/editing form:</strong><br/>' 'If you have submitted a GPG key, you can upload a *.sig file, ' 'and your upload can be verified. You can also submit these checksum file types:<br/>' '*.md5, *.sha1, *.sha224, *.sha256, *.sha384 or *.sha512'), validators=[MaxLengthValidator(262144)], **null) last_seen = DateTimeField(**null) visits = IntegerField(default=0) def __str__(self): return self.name @property def name(self): if self.first_name or self.last_name: return self.get_full_name() return self.username class Meta: permissions = [ ("use_irc", _("IRC Chat Training Complete")), ("website_cla_agreed", _("Agree to Website License")), ] db_table = 'auth_user' def get_ircnick(self): if not self.ircnick: return self.username return self.ircnick def photo_url(self): if self.photo: return self.photo.url return None def photo_preview(self): if self.photo: return '<img src="%s" style="max-width: 200px; max-height: 250px;"/>' % self.photo.url # Return an embedded svg, it's easier than dealing with static files. return """ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="200" height="250"> <path style="stroke:#6c6c6c;stroke-width:.5px;fill:#ece8e6;" d="m1.2 1.2v248h27.6c-9.1-43 8-102 40.9-123-49.5-101 111-99.9 61.5 1.18 36.6 35.4 48.6 78.1 39.1 122h28.5v-248z" /></svg>""" photo_preview.allow_tags = True def quota(self): from resources.models import Quota groups = Q(group__in=self.groups.all()) | Q(group__isnull=True) quotas = Quota.objects.filter(groups) if quotas.count(): return quotas.aggregate(Max('size'))['size__max'] * 1024 return 0 def get_absolute_url(self): if not self.username: return '/' return reverse('view_profile', kwargs={'username':self.username}) def is_moderator(self): return self.has_perm("moderation.can_moderate") def visited_by(self, by_user): if by_user != self: self.visits += 1 self.save(update_fields=['visits']) @property def teams(self): return Team.objects.filter(group__in=self.groups.all()) def viewer_is_subscribed(self): from cms.utils.permissions import get_current_user as get_user user = get_user() if user.is_authenticated(): return bool(self.resources.subscriptions().get(user=user.pk)) return False