示例#1
0
文件: models.py 项目: zzdjk6/kuma
class UserProfile(ModelBase):
    """
    The UserProfile *must* exist for each
    django.contrib.auth.models.User object. This may be relaxed
    once Dekiwiki isn't the definitive db for user info.

    timezone and language fields are syndicated to Dekiwiki
    """

    # Website fields defined for the profile form
    # TODO: Someday this will probably need to allow arbitrary per-profile
    # entries, and these will just be suggestions.
    website_choices = [
        ('website',
         dict(
             label=_(u'Website'),
             prefix='http://',
             regex='^https?://',
         )),
        ('twitter',
         dict(
             label=_(u'Twitter'),
             prefix='http://twitter.com/',
             regex='^https?://twitter.com/',
         )),
        ('github',
         dict(
             label=_(u'GitHub'),
             prefix='http://github.com/',
             regex='^https?://github.com/',
         )),
        ('stackoverflow',
         dict(
             label=_(u'StackOverflow'),
             prefix='http://stackoverflow.com/users/',
             regex='^https?://stackoverflow.com/users/',
         )),
        ('linkedin',
         dict(
             label=_(u'LinkedIn'),
             prefix='http://www.linkedin.com/in/',
             regex='^https?://www.linkedin.com/in/',
         )),
    ]

    class Meta:
        db_table = 'user_profiles'

    # This could be a ForeignKey, except wikidb might be
    # a different db
    deki_user_id = models.PositiveIntegerField(default=0, editable=False)
    timezone = TimeZoneField(null=True,
                             blank=True,
                             verbose_name=_(u'Timezone'))
    locale = LocaleField(null=True,
                         blank=True,
                         db_index=True,
                         verbose_name=_(u'Language'))
    homepage = models.URLField(max_length=255,
                               blank=True,
                               default='',
                               error_messages={
                                   'invalid':
                                   _(u'This URL has an invalid format. '
                                     u'Valid URLs look like '
                                     u'http://example.com/my_page.')
                               })
    title = models.CharField(_(u'Title'),
                             max_length=255,
                             default='',
                             blank=True)
    fullname = models.CharField(_(u'Name'),
                                max_length=255,
                                default='',
                                blank=True)
    organization = models.CharField(_(u'Organization'),
                                    max_length=255,
                                    default='',
                                    blank=True)
    location = models.CharField(_(u'Location'),
                                max_length=255,
                                default='',
                                blank=True)
    bio = models.TextField(_(u'About Me'), blank=True)

    irc_nickname = models.CharField(_(u'IRC nickname'),
                                    max_length=255,
                                    default='',
                                    blank=True)

    tags = NamespacedTaggableManager(_(u'Tags'), blank=True)

    # should this user receive contentflagging emails?
    content_flagging_email = models.BooleanField(default=False)
    user = models.ForeignKey(DjangoUser, null=True, editable=False, blank=True)

    # HACK: Grab-bag field for future expansion in profiles
    # We can store arbitrary data in here and later migrate to relational
    # tables if the data ever needs to be indexed & queried. Otherwise,
    # this keeps things nicely denormalized. Ideally, access to this field
    # should be gated through accessors on the model to make that transition
    # easier.
    misc = JSONField(blank=True, null=True)

    @models.permalink
    def get_absolute_url(self):
        return ('devmo.views.profile_view', [self.user.username])

    @property
    def websites(self):
        if 'websites' not in self.misc:
            self.misc['websites'] = {}
        return self.misc['websites']

    @websites.setter
    def websites(self, value):
        self.misc['websites'] = value

    _deki_user = None

    @property
    def deki_user(self):
        if not settings.DEKIWIKI_ENDPOINT:
            # There is no deki_user, if the MindTouch API is disabled.
            return None
        if not self._deki_user:
            # Need to find the DekiUser corresponding to the ID
            from dekicompat.backends import DekiUserBackend
            self._deki_user = (DekiUserBackend().get_deki_user(
                self.deki_user_id))
        return self._deki_user

    def gravatar_url(self,
                     secure=True,
                     size=220,
                     rating='pg',
                     default=DEFAULT_AVATAR):
        """Produce a gravatar image URL from email address."""
        base_url = (secure and 'https://secure.gravatar.com'
                    or 'http://www.gravatar.com')
        m = hashlib.md5(self.user.email.lower().encode('utf8'))
        return '%(base_url)s/avatar/%(hash)s?%(params)s' % dict(
            base_url=base_url,
            hash=m.hexdigest(),
            params=urllib.urlencode(dict(s=size, d=default, r=rating)))

    @property
    def gravatar(self):
        return self.gravatar_url()

    def __unicode__(self):
        return '%s: %s' % (self.id, self.deki_user_id)

    def allows_editing_by(self, user):
        if user == self.user:
            return True
        if user.is_staff or user.is_superuser:
            return True
        return False

    @property
    def mindtouch_language(self):
        if not self.locale:
            return ''
        return settings.LANGUAGE_DEKI_MAP[self.locale]

    @property
    def mindtouch_timezone(self):
        if not self.timezone:
            return ''
        base_seconds = self.timezone._utcoffset.days * 86400
        offset_seconds = self.timezone._utcoffset.seconds
        offset_hours = (base_seconds + offset_seconds) / 3600
        return "%03d:00" % offset_hours

    def save(self, *args, **kwargs):
        skip_mindtouch_put = kwargs.get('skip_mindtouch_put', False)
        if 'skip_mindtouch_put' in kwargs:
            del kwargs['skip_mindtouch_put']
        super(UserProfile, self).save(*args, **kwargs)
        if skip_mindtouch_put:
            return
        if not settings.DEKIWIKI_ENDPOINT:
            # Skip if the MindTouch API is unavailable
            return
        from dekicompat.backends import DekiUserBackend
        DekiUserBackend.put_mindtouch_user(self.user)

    def wiki_activity(self):
        return Revision.objects.filter(
            creator=self.user).order_by('-created')[:5]
示例#2
0
class Submission(models.Model):
    """Representation of a demo submission"""
    objects = SubmissionManager()
    admin_manager = models.Manager()

    title = models.CharField(_("what is your demo's name?"),
                             max_length=255,
                             blank=False,
                             unique=True)
    slug = models.SlugField(_("slug"), blank=False, unique=True, max_length=50)
    summary = models.CharField(_("describe your demo in one line"),
                               max_length=255,
                               blank=False)
    description = models.TextField(
        _("describe your demo in more detail (optional)"), blank=True)

    featured = models.BooleanField()
    hidden = models.BooleanField(_("Hide this demo from others?"),
                                 default=False)
    censored = models.BooleanField()
    censored_url = models.URLField(_("Redirect URL for censorship."),
                                   verify_exists=False,
                                   blank=True,
                                   null=True)

    navbar_optout = models.BooleanField(
        _('control how your demo is launched'),
        choices=((True,
                  _('Disable navigation bar, launch demo in a new window')),
                 (False, _('Use navigation bar, display demo in <iframe>'))))

    comments_total = models.PositiveIntegerField(default=0)

    launches = ActionCounterField()
    likes = ActionCounterField()

    taggit_tags = NamespacedTaggableManager(blank=True)

    screenshot_1 = ReplacingImageWithThumbField(
        _('Screenshot #1'),
        max_length=255,
        storage=demo_uploads_fs,
        upload_to=mk_upload_to('screenshot_1.png'),
        blank=False)
    screenshot_2 = ReplacingImageWithThumbField(
        _('Screenshot #2'),
        max_length=255,
        storage=demo_uploads_fs,
        upload_to=mk_upload_to('screenshot_2.png'),
        blank=True)
    screenshot_3 = ReplacingImageWithThumbField(
        _('Screenshot #3'),
        max_length=255,
        storage=demo_uploads_fs,
        upload_to=mk_upload_to('screenshot_3.png'),
        blank=True)
    screenshot_4 = ReplacingImageWithThumbField(
        _('Screenshot #4'),
        max_length=255,
        storage=demo_uploads_fs,
        upload_to=mk_upload_to('screenshot_4.png'),
        blank=True)
    screenshot_5 = ReplacingImageWithThumbField(
        _('Screenshot #5'),
        max_length=255,
        storage=demo_uploads_fs,
        upload_to=mk_upload_to('screenshot_5.png'),
        blank=True)

    video_url = VideoEmbedURLField(
        _("have a video of your demo in action? (optional)"),
        blank=True,
        null=True)

    demo_package = ReplacingZipFileField(
        _('select a ZIP file containing your demo'),
        max_length=255,
        max_upload_size=config_lazy('DEMO_MAX_ZIP_FILESIZE', 60 * 1024 *
                                    1024),  # overridden by constance
        storage=demo_uploads_fs,
        upload_to=mk_slug_upload_to('demo_package.zip'),
        blank=False)

    source_code_url = models.URLField(_(
        "Is your source code also available somewhere else on the web (e.g., github)? Please share the link."
    ),
                                      blank=True,
                                      null=True)
    license_name = models.CharField(
        _("Select the license that applies to your source code."),
        max_length=64,
        blank=False,
        choices=((x['name'], x['title']) for x in DEMO_LICENSES.values()))

    creator = models.ForeignKey(User, blank=False, null=True)

    created = models.DateTimeField(_('date created'),
                                   auto_now_add=True,
                                   blank=False)
    modified = models.DateTimeField(_('date last modified'),
                                    auto_now=True,
                                    blank=False)

    def natural_key(self):
        return (self.slug, )

    def update(self, **kw):
        """
        Shortcut for doing an UPDATE on this object.

        If _signal=False is in ``kw`` the post_save signal won't be sent.
        """
        signal = kw.pop('_signal', True)
        cls = self.__class__
        using = kw.pop('using', 'default')
        for k, v in kw.items():
            setattr(self, k, v)
        if signal:
            # Detect any attribute changes during pre_save and add those to the
            # update kwargs.
            attrs = dict(self.__dict__)
            models.signals.pre_save.send(sender=cls, instance=self)
            for k, v in self.__dict__.items():
                if attrs[k] != v:
                    kw[k] = v
                    setattr(self, k, v)
        cls.objects.using(using).filter(pk=self.pk).update(**kw)
        if signal:
            models.signals.post_save.send(sender=cls,
                                          instance=self,
                                          created=False)

    def censor(self, url=None):
        """Censor a demo, with optional link to explanation"""
        self.censored = True
        self.censored_url = url
        self.save()

        root = '%s/%s' % (DEMO_UPLOADS_ROOT, get_root_for_submission(self))
        if isdir(root):
            rmtree(root)

    def __unicode__(self):
        return 'Submission "%(title)s"' % dict(title=self.title)

    def get_absolute_url(self):
        return reverse('kuma.demos.views.detail', kwargs={'slug': self.slug})

    def _make_unique_slug(self, **kwargs):
        """
        Try to generate a unique 50-character slug.

        """
        if self.slug:
            slug = self.slug[:50]
        else:
            slug = slugify(self.title)[:50]
        using = kwargs['using'] if 'using' in kwargs else 'default'
        existing = Submission.objects.using(using).filter(slug=slug)
        if (not existing) or (self.id and self.id in [s.id for s in existing]):
            return slug
        # If the first 50 characters aren't unique, we chop off the
        # last two and try sticking a two-digit number there.
        #
        # If for some reason we get to 100 demos which all have the
        # same first fifty characters in their title, this will
        # break. Hopefully that's unlikely enough that it won't be a
        # problem, but we can always add a check at the end of the
        # while loop or come up with some other method if we actually
        # run into it.
        base_slug = slug[:-2]
        i = 0
        while Submission.objects.filter(slug=slug).exists() and i < 100:
            slug = "%s%02d" % (base_slug, i)
            i += 1
        return slug

    def save(self, **kwargs):
        """Save the submission, updating slug and screenshot thumbnails"""
        self.slug = self._make_unique_slug(**kwargs)
        super(Submission, self).save(**kwargs)

    def delete(self, using=None):
        root = '%s/%s' % (DEMO_UPLOADS_ROOT, get_root_for_submission(self))
        if isdir(root):
            rmtree(root)
        super(Submission, self).delete(using)

    def clean(self):
        if self.demo_package:
            Submission.validate_demo_zipfile(self.demo_package)

    def next(self):
        """Find the next submission by created time, return None if not found."""
        try:
            obj = self.get_next_by_created(hidden=False)
            return obj
        except Submission.DoesNotExist:
            return None

    def previous(self):
        """Find the previous submission by created time, return None if not found."""
        try:
            obj = self.get_previous_by_created(hidden=False)
            return obj
        except Submission.DoesNotExist:
            return None

    def screenshot_url(self, index='1'):
        """Fetch the screenshot URL for a given index, swallowing errors"""
        try:
            return getattr(self, 'screenshot_%s' % index).url
        except:
            return ''

    def thumbnail_url(self, index='1'):
        """Fetch the screenshot thumbnail URL for a given index, swallowing
        errors"""
        try:
            return getattr(self, 'screenshot_%s' % index).thumbnail_url()
        except:
            return ''

    def get_flags(self):
        """
        Assemble status flags, based on featured status and a set of special
        tags (eg. for Dev Derby). The flags are assembled in order of display
        priority, so the first flag on the list (if any) is the most
        important"""
        flags = []

        # Iterate through known flags based on tag naming convention. Tag flags
        # are listed here in order of priority.
        tag_flags = ('firstplace', 'secondplace', 'thirdplace', 'finalist')

        or_queries = []
        for tag_flag in tag_flags:
            term = 'system:challenge:%s:' % tag_flag
            or_queries.append(Q(**{'name__startswith': term}))

        for tag in self.taggit_tags.filter(reduce(operator.or_, or_queries)):
            split_tag_name = tag.name.split(':')
            if len(split_tag_name
                   ) > 2:  # the first two items are ['system', 'challenge']
                flags.append(
                    split_tag_name[2])  # the third item is the tag name

        # Featured is an odd-man-out before we had tags
        if self.featured:
            flags.append('featured')

        return flags

    def is_derby_submission(self):
        return bool(self.taggit_tags.all_ns('challenge:'))

    def challenge_closed(self):
        challenge_tags = self.taggit_tags.all_ns('challenge:')
        if not challenge_tags or 'challenge:none' in map(str, challenge_tags):
            return False
        return challenge_utils.challenge_closed(challenge_tags)

    @classmethod
    def allows_listing_hidden_by(cls, user):
        if user.is_staff or user.is_superuser:
            return True
        return False

    def allows_hiding_by(self, user):
        if user.is_staff or user.is_superuser or user == self.creator:
            return True
        return False

    def allows_viewing_by(self, user):
        if not self.censored:
            if user.is_staff or user.is_superuser or user == self.creator:
                return True
            if not self.hidden:
                return True
        return False

    def allows_editing_by(self, user):
        if user.is_staff or user.is_superuser or user == self.creator:
            return True
        return False

    def allows_deletion_by(self, user):
        if user.is_staff or user.is_superuser or user == self.creator:
            return True
        return False

    @classmethod
    def get_valid_demo_zipfile_entries(cls, zf):
        """Filter a ZIP file's entries for only accepted entries"""
        # TODO: Move to zip file field?
        return [
            x for x in zf.infolist()
            if not (x.filename.startswith('/') or '/..' in x.filename)
            and not (basename(x.filename).startswith('.')) and x.file_size > 0
        ]

    @classmethod
    def validate_demo_zipfile(cls, file):
        """Ensure a given file is a valid ZIP file without disallowed file
        entries and with an HTML index."""
        # TODO: Move to zip file field?
        try:
            zf = zipfile.ZipFile(file)
        except:
            raise ValidationError(_('ZIP file contains no acceptable files'))

        if zf.testzip():
            raise ValidationError(_('ZIP file corrupted'))

        valid_entries = Submission.get_valid_demo_zipfile_entries(zf)
        if len(valid_entries) == 0:
            raise ValidationError(_('ZIP file contains no acceptable files'))

        m_mime = magic.Magic(mime=True)

        index_found = False
        for zi in valid_entries:
            name = zi.filename

            # HACK: We're accepting {index,demo}.html as the root index and
            # normalizing on unpack
            if 'index.html' == name or 'demo.html' == name:
                index_found = True

            if zi.file_size > constance.config.DEMO_MAX_FILESIZE_IN_ZIP:
                raise ValidationError(
                    _('ZIP file contains a file that is too large: %(filename)s'
                      ) % {"filename": name})

            file_data = zf.read(zi)
            # HACK: Sometimes we get "type; charset", even if charset wasn't asked for
            file_mime_type = m_mime.from_buffer(file_data).split(';')[0]

            extensions = constance.config.DEMO_BLACKLIST_OVERRIDE_EXTENSIONS.split(
            )
            override_file_extensions = [
                '.%s' % extension for extension in extensions
            ]

            if (file_mime_type in DEMO_MIMETYPE_BLACKLIST
                    and not name.endswith(tuple(override_file_extensions))):
                raise ValidationError(
                    _('ZIP file contains an unacceptable file: %(filename)s') %
                    {'filename': name})

        if not index_found:
            raise ValidationError(_('HTML index not found in ZIP'))

    def process_demo_package(self):
        """Unpack the demo ZIP file into the appropriate directory, filtering
        out any invalid file entries and normalizing demo.html to index.html if
        present."""
        # TODO: Move to zip file field?

        # Derive a directory name from the zip filename, clean up any existing
        # directory before unpacking.
        new_root_dir = self.demo_package.path.replace('.zip', '')
        if isdir(new_root_dir):
            rmtree(new_root_dir)

        # Load up the zip file and extract the valid entries
        zf = zipfile.ZipFile(self.demo_package.file)
        valid_entries = Submission.get_valid_demo_zipfile_entries(zf)

        for zi in valid_entries:
            if type(zi.filename) is unicode:
                zi_filename = zi.filename
            else:
                zi_filename = zi.filename.decode('utf-8', 'ignore')

            # HACK: Normalize demo.html to index.html
            if zi_filename == u'demo.html':
                zi_filename = u'index.html'

            # Relocate all files from detected root dir to a directory named
            # for the zip file in storage
            out_fn = u'%s/%s' % (new_root_dir, zi_filename)
            out_dir = dirname(out_fn)

            # Create parent directories where necessary.
            if not isdir(out_dir):
                makedirs(out_dir.encode('utf-8'), 0775)

            # Extract the file from the zip into the desired location.
            fout = open(out_fn.encode('utf-8'), 'wb')
            copyfileobj(zf.open(zi), fout)
示例#3
0
文件: models.py 项目: gerv/kuma
class Food(models.Model):
    name = models.CharField(max_length=50)
    tags = NamespacedTaggableManager()

    def __unicode__(self):
        return self.name
示例#4
0
文件: models.py 项目: tantek/kuma
class UserProfile(ModelBase):
    """
    Want to track some data that isn't in dekiwiki's db?
    This is the proper grab bag for user profile info.

    Also, dekicompat middleware and backends use this
    class to find Django user objects.

    The UserProfile *must* exist for each
    django.contrib.auth.models.User object. This may be relaxed
    once Dekiwiki isn't the definitive db for user info.
    """

    # Website fields defined for the profile form
    # TODO: Someday this will probably need to allow arbitrary per-profile
    # entries, and these will just be suggestions.
    website_choices = [
        ('website', dict(
            label=_('Website'),
            prefix='http://',
        )),
        ('twitter', dict(
            label=_('Twitter'),
            prefix='http://twitter.com/',
        )),
        ('github', dict(
            label=_('GitHub'),
            prefix='http://github.com/',
        )),
        ('stackoverflow',
         dict(
             label=_('StackOverflow'),
             prefix='http://stackoverflow.com/users/',
         )),
        ('linkedin',
         dict(
             label=_('LinkedIn'),
             prefix='http://www.linkedin.com/in/',
         )),
    ]

    class Meta:
        db_table = 'user_profiles'

    # This could be a ForeignKey, except wikidb might be
    # a different db
    deki_user_id = models.PositiveIntegerField(default=0, editable=False)
    homepage = models.URLField(max_length=255,
                               blank=True,
                               default='',
                               verify_exists=False,
                               error_messages={
                                   'invalid':
                                   _('This URL has an invalid format. '
                                     'Valid URLs look like '
                                     'http://example.com/my_page.')
                               })
    title = models.CharField(_('Title'),
                             max_length=255,
                             default='',
                             blank=True)
    fullname = models.CharField(_('Name'),
                                max_length=255,
                                default='',
                                blank=True)
    organization = models.CharField(_('Organization'),
                                    max_length=255,
                                    default='',
                                    blank=True)
    location = models.CharField(_('Location'),
                                max_length=255,
                                default='',
                                blank=True)
    bio = models.TextField(_('About Me'), blank=True)

    irc_nickname = models.CharField(_('IRC nickname'),
                                    max_length=255,
                                    default='',
                                    blank=True)

    tags = NamespacedTaggableManager(_('Tags'), blank=True)

    # should this user receive contentflagging emails?
    content_flagging_email = models.BooleanField(default=False)
    user = models.ForeignKey(DjangoUser, null=True, editable=False, blank=True)

    # HACK: Grab-bag field for future expansion in profiles
    # We can store arbitrary data in here and later migrate to relational
    # tables if the data ever needs to be indexed & queried. Otherwise,
    # this keeps things nicely denormalized. Ideally, access to this field
    # should be gated through accessors on the model to make that transition
    # easier.
    misc = JSONField(blank=True, null=True)

    @property
    def websites(self):
        if 'websites' not in self.misc:
            self.misc['websites'] = {}
        return self.misc['websites']

    @websites.setter
    def websites(self, value):
        self.misc['websites'] = value

    _deki_user = None

    @property
    def deki_user(self):
        if not self._deki_user:
            # Need to find the DekiUser corresponding to the ID
            from dekicompat.backends import DekiUserBackend
            self._deki_user = (DekiUserBackend().get_deki_user(
                self.deki_user_id))
        return self._deki_user

    def gravatar_url(
            self,
            secure=True,
            size=220,
            rating='pg',
            default='http://developer.mozilla.org/media/img/avatar.png'):
        """Produce a gravatar image URL from email address."""
        base_url = (secure and 'https://secure.gravatar.com'
                    or 'http://www.gravatar.com')
        m = hashlib.md5(self.user.email)
        return '%(base_url)s/avatar/%(hash)s?%(params)s' % dict(
            base_url=base_url,
            hash=m.hexdigest(),
            params=urllib.urlencode(dict(s=size, d=default, r=rating)))

    @property
    def gravatar(self):
        return self.gravatar_url()

    def __unicode__(self):
        return '%s: %s' % (self.id, self.deki_user_id)

    def allows_editing_by(self, user):
        if user == self.user:
            return True
        if user.is_staff or user.is_superuser:
            return True
        return False
示例#5
0
class UserProfile(ModelBase):
    """
    The UserProfile *must* exist for each
    django.contrib.auth.models.User object. This may be relaxed
    once Dekiwiki isn't the definitive db for user info.

    timezone and language fields are syndicated to Dekiwiki
    """
    # Website fields defined for the profile form
    # TODO: Someday this will probably need to allow arbitrary per-profile
    # entries, and these will just be suggestions.
    website_choices = [('website',
                        dict(
                            label=_(u'Website'),
                            prefix='http://',
                            regex='^https?://',
                            fa_icon='icon-link',
                        )),
                       ('twitter',
                        dict(
                            label=_(u'Twitter'),
                            prefix='https://twitter.com/',
                            regex='^https?://twitter.com/',
                            fa_icon='icon-twitter',
                        )),
                       ('github',
                        dict(
                            label=_(u'GitHub'),
                            prefix='https://github.com/',
                            regex='^https?://github.com/',
                            fa_icon='icon-github',
                        )),
                       ('stackoverflow',
                        dict(
                            label=_(u'Stack Overflow'),
                            prefix='https://stackoverflow.com/users/',
                            regex='^https?://stackoverflow.com/users/',
                            fa_icon='icon-stackexchange',
                        )),
                       ('linkedin',
                        dict(
                            label=_(u'LinkedIn'),
                            prefix='https://www.linkedin.com/in/',
                            regex='^https?://www.linkedin.com/in/',
                            fa_icon='icon-linkedin',
                        )),
                       ('mozillians',
                        dict(
                            label=_(u'Mozillians'),
                            prefix='https://mozillians.org/u/',
                            regex='^https?://mozillians.org/u/',
                            fa_icon='icon-group',
                        )),
                       ('facebook',
                        dict(
                            label=_(u'Facebook'),
                            prefix='https://www.facebook.com/',
                            regex='^https?://www.facebook.com/',
                            fa_icon='icon-facebook',
                        ))]
    # This could be a ForeignKey, except wikidb might be
    # a different db
    deki_user_id = models.PositiveIntegerField(default=0, editable=False)
    timezone = TimeZoneField(null=True,
                             blank=True,
                             verbose_name=_(u'Timezone'))
    locale = LocaleField(null=True,
                         blank=True,
                         db_index=True,
                         verbose_name=_(u'Language'))
    homepage = models.URLField(max_length=255,
                               blank=True,
                               default='',
                               error_messages={
                                   'invalid':
                                   _(u'This URL has an invalid format. '
                                     u'Valid URLs look like '
                                     u'http://example.com/my_page.')
                               })
    title = models.CharField(_(u'Title'),
                             max_length=255,
                             default='',
                             blank=True)
    fullname = models.CharField(_(u'Name'),
                                max_length=255,
                                default='',
                                blank=True)
    organization = models.CharField(_(u'Organization'),
                                    max_length=255,
                                    default='',
                                    blank=True)
    location = models.CharField(_(u'Location'),
                                max_length=255,
                                default='',
                                blank=True)
    bio = models.TextField(_(u'About Me'), blank=True)

    irc_nickname = models.CharField(_(u'IRC nickname'),
                                    max_length=255,
                                    default='',
                                    blank=True)

    tags = NamespacedTaggableManager(_(u'Tags'), blank=True)

    # should this user receive contentflagging emails?
    content_flagging_email = models.BooleanField(default=False)
    user = models.ForeignKey(User, null=True, editable=False, blank=True)

    # HACK: Grab-bag field for future expansion in profiles
    # We can store arbitrary data in here and later migrate to relational
    # tables if the data ever needs to be indexed & queried. Otherwise,
    # this keeps things nicely denormalized. Ideally, access to this field
    # should be gated through accessors on the model to make that transition
    # easier.
    misc = JSONField(blank=True, null=True)

    class Meta:
        db_table = 'user_profiles'

    def __unicode__(self):
        return '%s: %s' % (self.id, self.deki_user_id)

    def get_absolute_url(self):
        return self.user.get_absolute_url()

    @property
    def websites(self):
        if 'websites' not in self.misc:
            self.misc['websites'] = {}
        return self.misc['websites']

    @websites.setter
    def websites(self, value):
        self.misc['websites'] = value

    @cached_property
    def beta_tester(self):
        return (constance.config.BETA_GROUP_NAME
                in self.user.groups.values_list('name', flat=True))

    @property
    def gravatar(self):
        return gravatar_url(self.user)

    def allows_editing_by(self, user):
        if user == self.user:
            return True
        if user.is_staff or user.is_superuser:
            return True
        return False

    def wiki_activity(self):
        return (Revision.objects.filter(
            creator=self.user).order_by('-created')[:5])
示例#6
0
文件: models.py 项目: tantek/kuma
class Submission(models.Model):
    """Representation of a demo submission"""
    objects = SubmissionManager()
    admin_manager = models.Manager()

    title = models.CharField(_("what is your demo's name?"),
                             max_length=255,
                             blank=False,
                             unique=True)
    slug = models.SlugField(_("slug"), blank=False, unique=True)
    summary = models.CharField(_("describe your demo in one line"),
                               max_length=255,
                               blank=False)
    description = models.TextField(
        _("describe your demo in more detail (optional)"), blank=True)

    featured = models.BooleanField()
    hidden = models.BooleanField(_("Hide this demo from others?"),
                                 default=False)
    censored = models.BooleanField()
    censored_url = models.URLField(_("Redirect URL for censorship."),
                                   verify_exists=False,
                                   blank=True,
                                   null=True)

    navbar_optout = models.BooleanField(
        _('control how your demo is launched'),
        choices=((True,
                  _('Disable navigation bar, launch demo in a new window')),
                 (False, _('Use navigation bar, display demo in <iframe>'))))

    comments_total = models.PositiveIntegerField(default=0)

    launches = ActionCounterField()
    likes = ActionCounterField()

    taggit_tags = NamespacedTaggableManager(blank=True)

    screenshot_1 = ReplacingImageWithThumbField(
        _('Screenshot #1'),
        max_length=255,
        storage=demo_uploads_fs,
        upload_to=mk_upload_to('screenshot_1.png'),
        blank=False)
    screenshot_2 = ReplacingImageWithThumbField(
        _('Screenshot #2'),
        max_length=255,
        storage=demo_uploads_fs,
        upload_to=mk_upload_to('screenshot_2.png'),
        blank=True)
    screenshot_3 = ReplacingImageWithThumbField(
        _('Screenshot #3'),
        max_length=255,
        storage=demo_uploads_fs,
        upload_to=mk_upload_to('screenshot_3.png'),
        blank=True)
    screenshot_4 = ReplacingImageWithThumbField(
        _('Screenshot #4'),
        max_length=255,
        storage=demo_uploads_fs,
        upload_to=mk_upload_to('screenshot_4.png'),
        blank=True)
    screenshot_5 = ReplacingImageWithThumbField(
        _('Screenshot #5'),
        max_length=255,
        storage=demo_uploads_fs,
        upload_to=mk_upload_to('screenshot_5.png'),
        blank=True)

    video_url = VideoEmbedURLField(
        _("have a video of your demo in action? (optional)"),
        verify_exists=False,
        blank=True,
        null=True)

    demo_package = ReplacingZipFileField(
        _('select a ZIP file containing your demo'),
        max_length=255,
        max_upload_size=DEMO_MAX_ZIP_FILESIZE,
        storage=demo_uploads_fs,
        upload_to=mk_slug_upload_to('demo_package.zip'),
        blank=False)

    source_code_url = models.URLField(_(
        "Is your source code also available somewhere else on the web (e.g., github)? Please share the link."
    ),
                                      verify_exists=False,
                                      blank=True,
                                      null=True)
    license_name = models.CharField(
        _("Select the license that applies to your source code."),
        max_length=64,
        blank=False,
        choices=((x['name'], x['title']) for x in DEMO_LICENSES.values()))

    creator = models.ForeignKey(User, blank=False, null=True)

    created = models.DateTimeField(_('date created'),
                                   auto_now_add=True,
                                   blank=False)
    modified = models.DateTimeField(_('date last modified'),
                                    auto_now=True,
                                    blank=False)

    def __unicode__(self):
        return 'Submission "%(title)s"' % dict(title=self.title)

    def get_absolute_url(self):
        return reverse('demos.views.detail', kwargs={'slug': self.slug})

    def save(self):
        """Save the submission, updating slug and screenshot thumbnails"""
        self.slug = slugify(self.title)
        super(Submission, self).save()

    def delete(self, using=None):
        root = '%s/%s' % (settings.MEDIA_ROOT, get_root_for_submission(self))
        if isdir(root): rmtree(root)
        super(Submission, self).delete(using)

    def clean(self):
        if self.demo_package:
            Submission.validate_demo_zipfile(self.demo_package)

    def next(self):
        """Find the next submission by created time, return None if not found."""
        try:
            obj = self.get_next_by_created()
            return obj
        except Submission.DoesNotExist:
            return None

    def previous(self):
        """Find the previous submission by created time, return None if not found."""
        try:
            obj = self.get_previous_by_created()
            return obj
        except Submission.DoesNotExist:
            return None

    def thumbnail_url(self, index='1'):
        return getattr(self, 'screenshot_%s' % index).url.replace(
            'screenshot', 'screenshot_thumb')

    def get_flags(self):
        """Assemble status flags, based on featured status and a set of special
        tags (eg. for Dev Derby). The flags are assembled in order of display
        priority, so the first flag on the list (if any) is the most
        important"""
        flags = []

        # Iterate through known flags based on tag naming convention. Tag flags
        # are listed here in order of priority.
        tag_flags = ('firstplace', 'secondplace', 'thirdplace', 'finalist')
        for p in tag_flags:
            for tag in self.taggit_tags.all():
                # TODO: Is this 'system:challenge' too hard-codey?
                if tag.name.startswith('system:challenge:%s:' % p):
                    flags.append(p)

        # Featured is an odd-man-out before we had tags
        if self.featured:
            flags.append('featured')

        return flags

    @classmethod
    def allows_listing_hidden_by(cls, user):
        if user.is_staff or user.is_superuser:
            return True
        return False

    def allows_hiding_by(self, user):
        if user.is_staff or user.is_superuser or user == self.creator:
            return True
        return False

    def allows_viewing_by(self, user):
        if not self.censored:
            if user.is_staff or user.is_superuser or user == self.creator:
                return True
            if not self.hidden:
                return True
        return False

    def allows_editing_by(self, user):
        if user.is_staff or user.is_superuser or user == self.creator:
            return True
        return False

    def allows_deletion_by(self, user):
        if user.is_staff or user.is_superuser or user == self.creator:
            return True
        return False

    @classmethod
    def get_valid_demo_zipfile_entries(cls, zf):
        """Filter a ZIP file's entries for only accepted entries"""
        # TODO: Move to zip file field?
        return [
            x for x in zf.infolist()
            if not (x.filename.startswith('/') or '/..' in x.filename)
            and not (basename(x.filename).startswith('.')) and x.file_size > 0
        ]

    @classmethod
    def validate_demo_zipfile(cls, file):
        """Ensure a given file is a valid ZIP file without disallowed file
        entries and with an HTML index."""
        # TODO: Move to zip file field?
        try:
            zf = zipfile.ZipFile(file)
        except:
            raise ValidationError(_('ZIP file contains no acceptable files'))

        if zf.testzip():
            raise ValidationError(_('ZIP file corrupted'))

        valid_entries = Submission.get_valid_demo_zipfile_entries(zf)
        if len(valid_entries) == 0:
            raise ValidationError(_('ZIP file contains no acceptable files'))

        m_mime = magic.Magic(mime=True)

        index_found = False
        for zi in valid_entries:
            name = zi.filename

            # HACK: We're accepting {index,demo}.html as the root index and
            # normalizing on unpack
            if 'index.html' == name or 'demo.html' == name:
                index_found = True

            if zi.file_size > DEMO_MAX_FILESIZE_IN_ZIP:
                raise ValidationError(
                    _('ZIP file contains a file that is too large: %(filename)s'
                      ) % {"filename": name})

            file_data = zf.read(zi)
            # HACK: Sometimes we get "type; charset", even if charset wasn't asked for
            file_mime_type = m_mime.from_buffer(file_data).split(';')[0]

            if file_mime_type in DEMO_MIMETYPE_BLACKLIST:
                raise ValidationError(
                    _('ZIP file contains an unacceptable file: %(filename)s') %
                    {"filename": name})

        if not index_found:
            raise ValidationError(_('HTML index not found in ZIP'))

    def process_demo_package(self):
        """Unpack the demo ZIP file into the appropriate directory, filtering
        out any invalid file entries and normalizing demo.html to index.html if
        present."""
        # TODO: Move to zip file field?

        # Derive a directory name from the zip filename, clean up any existing
        # directory before unpacking.
        new_root_dir = self.demo_package.path.replace('.zip', '')
        if isdir(new_root_dir):
            rmtree(new_root_dir)

        # Load up the zip file and extract the valid entries
        zf = zipfile.ZipFile(self.demo_package.file)
        valid_entries = Submission.get_valid_demo_zipfile_entries(zf)

        for zi in valid_entries:

            # HACK: Normalize demo.html to index.html
            if zi.filename == 'demo.html':
                zi.filename = 'index.html'

            # Relocate all files from detected root dir to a directory named
            # for the zip file in storage
            out_fn = '%s/%s' % (new_root_dir, zi.filename)
            out_dir = dirname(out_fn)

            # Create parent directories where necessary.
            if not isdir(out_dir):
                makedirs(out_dir, 0775)

            # Extract the file from the zip into the desired location.
            open(out_fn, 'w').write(zf.read(zi))