示例#1
0
class SluggedTestNoOverwriteOnAddModel(models.Model):
    title = models.CharField(max_length=42)
    slug = AutoSlugField(populate_from='title', overwrite_on_add=False)

    class Meta:
        app_label = 'django_extensions'
示例#2
0
class Grant(SuperModel):
    """Define the structure of a Grant."""
    class Meta:
        """Define the metadata for Grant."""

        ordering = ['-created_on']

    active = models.BooleanField(
        default=True, help_text=_('Whether or not the Grant is active.'))
    title = models.CharField(default='',
                             max_length=255,
                             help_text=_('The title of the Grant.'))
    slug = AutoSlugField(populate_from='title')
    description = models.TextField(
        default='', blank=True, help_text=_('The description of the Grant.'))
    reference_url = models.URLField(
        blank=True, help_text=_('The associated reference URL of the Grant.'))
    logo = models.ImageField(
        upload_to=get_upload_filename,
        null=True,
        blank=True,
        help_text=_('The Grant logo image.'),
    )
    logo_svg = models.FileField(
        upload_to=get_upload_filename,
        null=True,
        blank=True,
        help_text=_('The Grant logo SVG.'),
    )
    admin_address = models.CharField(
        max_length=255,
        default='0x0',
        help_text=_(
            'The wallet address where subscription funds will be sent.'),
    )
    contract_owner_address = models.CharField(
        max_length=255,
        default='0x0',
        help_text=
        _('The wallet address that owns the subscription contract and is able to call endContract()'
          ),
    )
    amount_goal = models.DecimalField(
        default=1,
        decimal_places=4,
        max_digits=50,
        help_text=_(
            'The monthly contribution goal amount for the Grant in DAI.'),
    )
    monthly_amount_subscribed = models.DecimalField(
        default=0,
        decimal_places=4,
        max_digits=50,
        help_text=_('The monthly subscribed to by contributors USDT/DAI.'),
    )
    amount_received = models.DecimalField(
        default=0,
        decimal_places=4,
        max_digits=50,
        help_text=_('The total amount received for the Grant in USDT/DAI.'),
    )
    token_address = models.CharField(
        max_length=255,
        default='0x0',
        help_text=_('The token address to be used with the Grant.'),
    )
    token_symbol = models.CharField(
        max_length=255,
        default='',
        help_text=_('The token symbol to be used with the Grant.'),
    )
    contract_address = models.CharField(
        max_length=255,
        default='0x0',
        help_text=_('The contract address of the Grant.'),
    )
    deploy_tx_id = models.CharField(
        max_length=255,
        default='0x0',
        help_text=_('The transaction id for contract deployment.'),
    )
    cancel_tx_id = models.CharField(
        max_length=255,
        default='0x0',
        help_text=_('The transaction id for endContract.'),
    )
    contract_version = models.DecimalField(
        default=0,
        decimal_places=0,
        max_digits=3,
        help_text=_('The contract version the Grant.'),
    )
    metadata = JSONField(
        default=dict,
        blank=True,
        help_text=
        _('The Grant metadata. Includes creation and last synced block numbers.'
          ),
    )
    network = models.CharField(
        max_length=8,
        default='mainnet',
        help_text=_('The network in which the Grant contract resides.'),
    )
    required_gas_price = models.DecimalField(
        default='0',
        decimal_places=0,
        max_digits=50,
        help_text=_('The required gas price for the Grant.'),
    )
    admin_profile = models.ForeignKey(
        'dashboard.Profile',
        related_name='grant_admin',
        on_delete=models.CASCADE,
        help_text=_('The Grant administrator\'s profile.'),
        null=True,
    )
    request_ownership_change = models.ForeignKey(
        'dashboard.Profile',
        related_name='request_ownership_change',
        on_delete=models.CASCADE,
        help_text=_('The Grant\'s potential new administrator profile.'),
        null=True,
    )
    team_members = models.ManyToManyField(
        'dashboard.Profile',
        related_name='grant_teams',
        help_text=_('The team members contributing to this Grant.'),
    )
    image_css = models.CharField(
        default='',
        blank=True,
        max_length=255,
        help_text=_('additional CSS to attach to the grant-banner img.'))
    clr_matching = models.DecimalField(
        default=0,
        decimal_places=2,
        max_digits=20,
        help_text=_('The CLR matching amount'),
    )
    activeSubscriptions = ArrayField(models.CharField(max_length=200),
                                     blank=True,
                                     default=list)
    hidden = models.BooleanField(
        default=False, help_text=_('Hide the grant from the /grants page?'))

    # Grant Query Set used as manager.
    objects = GrantQuerySet.as_manager()

    def __str__(self):
        """Return the string representation of a Grant."""
        return f"id: {self.pk}, active: {self.active}, title: {self.title}"

    def percentage_done(self):
        """Return the percentage of token received based on the token goal."""
        if not self.amount_goal:
            return 0
        return ((self.amount_received / self.amount_goal) * 100)

    def updateActiveSubscriptions(self):
        """updates the active subscriptions list"""
        handles = []
        for handle in Subscription.objects.filter(
                grant=self,
                active=True).distinct('contributor_profile').values_list(
                    'contributor_profile__handle', flat=True):
            handles.append(handle)
        self.activeSubscriptions = handles

    @property
    def abi(self):
        """Return grants abi."""
        from grants.abi import abi_v0
        return abi_v0

    @property
    def url(self):
        """Return grants url."""
        from django.urls import reverse
        return reverse('grants:details',
                       kwargs={
                           'grant_id': self.pk,
                           'grant_slug': self.slug
                       })

    @property
    def contract(self):
        """Return grants contract."""
        from dashboard.utils import get_web3
        web3 = get_web3(self.network)
        grant_contract = web3.eth.contract(Web3.toChecksumAddress(
            self.contract_address),
                                           abi=self.abi)
        return grant_contract
示例#3
0
class Playlist(MigrationMixin, TimestampedModelMixin, models.Model):

    TYPE_BASKET = 'basket'
    TYPE_PLAYLIST = 'playlist'
    TYPE_BROADCAST = 'broadcast'
    TYPE_OTHER = 'other'

    TYPE_CHOICES = (
        (TYPE_BASKET, _('Private Playlist')),
        (TYPE_PLAYLIST, _('Public Playlist')),
        (TYPE_BROADCAST, _('Broadcast')),
        (TYPE_OTHER, _('Other')),
    )

    name = models.CharField(max_length=200)
    slug = AutoSlugField(populate_from='name',
                         editable=True,
                         blank=True,
                         overwrite=True)
    uuid = models.UUIDField(default=uuid.uuid4, editable=False)

    status = models.PositiveIntegerField(
        default=0, choices=alibrary_settings.PLAYLIST_STATUS_CHOICES)
    type = models.CharField(max_length=12,
                            default='basket',
                            null=True,
                            choices=TYPE_CHOICES)
    broadcast_status = models.PositiveIntegerField(
        default=0, choices=alibrary_settings.PLAYLIST_BROADCAST_STATUS_CHOICES)
    broadcast_status_messages = JSONField(blank=True, null=True, default=None)

    playout_mode_random = models.BooleanField(
        verbose_name=_('Shuffle Playlist'),
        default=False,
        help_text=_(
            'If enabled the order of the tracks will be randomized for playout'
        ))

    rotation = models.BooleanField(default=True)
    rotation_date_start = models.DateField(verbose_name=_('Rotate from'),
                                           blank=True,
                                           null=True)
    rotation_date_end = models.DateField(verbose_name=_('Rotate until'),
                                         blank=True,
                                         null=True)

    main_image = models.ImageField(verbose_name=_('Image'),
                                   upload_to=upload_image_to,
                                   storage=OverwriteStorage(),
                                   null=True,
                                   blank=True)

    # relations
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             null=True,
                             blank=True,
                             default=None,
                             related_name='playlists')
    items = models.ManyToManyField('PlaylistItem',
                                   through='PlaylistItemPlaylist',
                                   blank=True)

    # tagging (d_tags = "display tags")
    d_tags = tagging.fields.TagField(max_length=1024,
                                     verbose_name='Tags',
                                     blank=True,
                                     null=True)

    # updated/calculated on save
    duration = models.IntegerField(null=True, default=0)

    target_duration = models.PositiveIntegerField(
        default=0,
        null=True,
        choices=alibrary_settings.PLAYLIST_TARGET_DURATION_CHOICES)

    dayparts = models.ManyToManyField(Daypart,
                                      blank=True,
                                      related_name='daypart_plalists')
    seasons = models.ManyToManyField('Season',
                                     blank=True,
                                     related_name='season_plalists')
    weather = models.ManyToManyField('Weather',
                                     blank=True,
                                     related_name='weather_plalists')

    # series
    series = models.ForeignKey(Series,
                               null=True,
                               blank=True,
                               on_delete=models.SET_NULL)
    series_number = models.PositiveIntegerField(null=True, blank=True)

    # is currently selected as default?
    is_current = models.BooleanField(_('Currently selected?'), default=False)

    description = extra.MarkdownTextField(blank=True, null=True)

    mixdown_file = models.FileField(null=True,
                                    blank=True,
                                    upload_to=upload_mixdown_to)

    emissions = GenericRelation('abcast.Emission')

    # meta
    class Meta:
        app_label = 'alibrary'
        verbose_name = _('Playlist')
        verbose_name_plural = _('Playlists')
        ordering = ('-updated', )

        permissions = (
            ('view_playlist', 'View Playlist'),
            ('edit_playlist', 'Edit Playlist'),
            ('schedule_playlist', 'Schedule Playlist'),
            ('admin_playlist', 'Edit Playlist (extended)'),
        )

    def __unicode__(self):
        return self.name

    def get_ct(self):
        return '{}.{}'.format(self._meta.app_label,
                              self.__class__.__name__).lower()

    def get_absolute_url(self):
        return reverse('alibrary-playlist-detail',
                       kwargs={
                           'slug': self.slug,
                       })

    def get_edit_url(self):
        return reverse("alibrary-playlist-edit", args=(self.pk, ))

    def get_delete_url(self):
        return reverse("alibrary-playlist-delete", args=(self.pk, ))

    def get_admin_url(self):
        return reverse("admin:alibrary_playlist_change", args=(self.pk, ))

    def get_duration(self):
        duration = 0
        try:
            for item in self.items.all():
                duration += item.content_object.get_duration()
                pip = PlaylistItemPlaylist.objects.get(playlist=self,
                                                       item=item)
                duration -= pip.cue_in
                duration -= pip.cue_out
                duration -= pip.fade_cross
        except:
            pass

        return duration

    # TODO: remove usages and use generic reverse 'emissions' instead
    def get_emissions(self):
        from abcast.models import Emission
        ctype = ContentType.objects.get_for_model(self)
        emissions = Emission.objects.filter(
            content_type__pk=ctype.id,
            object_id=self.id).order_by('-time_start')
        return emissions

    def get_api_url(self):
        return reverse('api_dispatch_detail',
                       kwargs={
                           'api_name': 'v1',
                           'resource_name': 'library/playlist',
                           'pk': self.pk
                       }) + ''

    def get_api_simple_url(self):
        return reverse('api_dispatch_detail',
                       kwargs={
                           'api_name': 'v1',
                           'resource_name': 'library/simpleplaylist',
                           'pk': self.pk
                       }) + ''

    def can_be_deleted(self):

        can_delete = False
        reason = _('This playlist cannot be deleted.')

        if self.type == 'basket':
            can_delete = True
            reason = None

        if self.type == 'playlist':
            can_delete = False
            reason = _(
                'Playlist "%s" is public. It cannot be deleted anymore.' %
                self.name)

        if self.type == 'broadcast':
            can_delete = False
            reason = _(
                'Playlist "%s" published for broadcast. It cannot be deleted anymore.'
                % self.name)

        return can_delete, reason

    def get_transform_status(self, target_type):
        """
        check if transformation is possible /
        what needs to be done
        Not so nicely here - but...
        """

        status = False
        """
        criterias = [
            {
                'key': 'tags',
                'name': _('Tags'),
                'status': True,
                'warning': _('Please add some tags'),
            },
            {
                'key': 'description',
                'name': _('Description'),
                'status': False,
                'warning': _('Please add a description'),
            }
        ]
        """

        criterias = []

        # "basket" only used while dev...
        if target_type == 'basket':
            status = True

        if target_type == 'playlist':
            status = True
            # tags
            tag_count = self.tags.count()
            if tag_count < 1:
                status = False
            criteria = {
                'key': 'tags',
                'name': _('Tags'),
                'status': tag_count > 0,
                'warning': _('Please add some tags'),
            }
            criterias.append(criteria)
            # scheduled
            if self.type == 'broadcast':
                schedule_count = self.get_emissions().count()
                if schedule_count > 0:
                    status = False
                criteria = {
                    'key':
                    'scheduled',
                    'name':
                    _('Playlist already scheduled')
                    if schedule_count > 0 else _('Playlist not scheduled'),
                    'status':
                    schedule_count < 1,
                    'warning':
                    _('This playlist has already ben scheduled %s times. Remove all scheduler entries to "un-broadcast" this playlist.'
                      % schedule_count),
                }
                if schedule_count > 0:
                    criterias.append(criteria)

        if target_type == 'broadcast':
            status = True
            # tags
            tag_count = self.tags.count()
            if tag_count < 1:
                status = False
            criteria = {
                'key': 'tags',
                'name': _('Tags'),
                'status': tag_count > 0,
                'warning': _('Please add some tags'),
            }
            criterias.append(criteria)

            # dayparts
            dp_count = self.dayparts.count()
            if not dp_count:
                status = False
            criteria = {
                'key': 'dayparts',
                'name': _('Dayparts'),
                'status': dp_count > 0,
                'warning': _('Please specify the dayparts'),
            }
            criterias.append(criteria)

            # duration
            if not self.broadcast_status == 1:
                status = False
            criteria = {
                'key': 'duration',
                'name': _('Duration'),
                'status': True if self.broadcast_status == 1 else False,
                'warning': _('Durations do not match'),
                # 'warning': ', '.join(self.broadcast_status_messages),
            }
            criterias.append(criteria)

        transformation = {'criterias': criterias, 'status': status}

        return transformation

    ###################################################################
    # legacy version - used in tastypie API (v1)
    ###################################################################
    def add_items_by_ids(self, ids, ct, timing=None):

        from alibrary.models.mediamodels import Media

        log.debug('add media to playlist: {}'.format(', '.join(ids)))

        for id in ids:
            id = int(id)

            co = None

            if ct == 'media':
                co = Media.objects.get(pk=id)

            if co:

                i = PlaylistItem(content_object=co)
                i.save()
                """
                ctype = ContentType.objects.get_for_model(co)
                item, created = PlaylistItem.objects.get_or_create(object_id=co.pk, content_type=ctype)
                """

                pi, created = PlaylistItemPlaylist.objects.get_or_create(
                    item=i, playlist=self, position=self.items.count())

                if timing:
                    try:
                        pi.fade_in = timing['fade_in']
                        pi.fade_out = timing['fade_out']
                        pi.cue_in = timing['cue_in']
                        pi.cue_out = timing['cue_out']
                        pi.save()
                    except:
                        pass

        self.save()

    ###################################################################
    # new version - used in DRF API (v245)
    ###################################################################
    def add_item(self, item, cue_and_fade=None, commit=True):

        log.debug('add item to playlist: {}'.format(item))

        playlist_item = PlaylistItem(content_object=item)
        playlist_item.save()

        playlist_item_playlist = PlaylistItemPlaylist(
            item=playlist_item, playlist=self, position=self.items.count())

        if cue_and_fade:
            playlist_item_playlist.fade_in = cue_and_fade['fade_in']
            playlist_item_playlist.fade_out = cue_and_fade['fade_out']
            playlist_item_playlist.cue_in = cue_and_fade['cue_in']
            playlist_item_playlist.cue_out = cue_and_fade['cue_out']

        playlist_item_playlist.save()

        if commit:
            self.save()

    def reorder_items_by_uuids(self, uuids):

        i = 0

        for uuid in uuids:
            pi = PlaylistItemPlaylist.objects.get(uuid=uuid)
            pi.position = i
            pi.save()

            i += 1

        self.save()

    def convert_to(self, playlist_type):

        log.debug('requested to convert "%s" from %s to %s' %
                  (self.name, self.type, playlist_type))

        if playlist_type == 'broadcast':
            self.broadcast_status, self.broadcast_status_messages = self.self_check(
            )

        transformation = self.get_transform_status(playlist_type)
        status = transformation['status']

        if playlist_type == 'broadcast' and status:
            _status, messages = self.self_check()
            if _status == 1:
                status = True

        if status:
            self.type = playlist_type
            self.created = timezone.now()
            self.save()

        return self, status

    def get_items(self):

        pis = PlaylistItemPlaylist.objects.filter(
            playlist=self).order_by('position')

        items = []
        for pi in pis:
            item = pi.item
            item.cue_in = pi.cue_in
            item.cue_out = pi.cue_out
            item.fade_in = pi.fade_in
            item.fade_out = pi.fade_out
            item.fade_cross = pi.fade_cross
            # get the actual playout duration
            try:
                # print '// getting duration for:'
                # print '%s - %s' % (item.content_object.pk, item.content_object.name)
                # print 'obj duration: %s' % item.content_object.duration_s
                item.playout_duration = item.content_object.duration_ms - item.cue_in - item.cue_out - item.fade_cross
            except Exception as e:
                log.warning('unable to get duration: {}'.format(e))
                item.playout_duration = 0

            items.append(item)
        return items

    def self_check(self):
        """
        check if everything is fine to be 'scheduled'
        """

        log.info('Self check requested for: %s' % self.name)

        status = 1  # set to 'OK'
        messages = []

        try:
            # check ready-status of related media
            for item in self.items.all():
                # log.debug('Self check content object: %s' % item.content_object)
                # log.debug('Self check master: %s' % item.content_object.master)
                # log.debug('Self check path: %s' % item.content_object.master.path)

                # check if file available
                try:
                    with open(item.content_object.master.path):
                        pass
                except IOError as e:
                    log.warning(
                        _('File does not exists: %s | %s') %
                        (e, item.content_object.master.path))
                    status = 99
                    messages.append(
                        _('File does not exists: %s | %s') %
                        (e, item.content_object.master.path))
                """
                pip = PlaylistItemPlaylist.objects.get(playlist=self, item=item)
                duration -= pip.cue_in
                duration -= pip.cue_out
                duration -= pip.fade_cross
                """

            # check duration & matching target_duration
            """
            compare durations. target: in seconds | calculated duration in milliseconds
            """
            diff = self.get_duration() - self.target_duration * 1000
            if abs(diff) > DURATION_MAX_DIFF:
                messages.append(
                    _('durations do not match. difference is: %s seconds' %
                      int(diff / 1000)))
                log.warning(
                    'durations do not match. difference is: %s seconds' %
                    int(diff / 1000))
                status = 2

        except Exception as e:
            messages.append(_('Validation error: %s ' % e))
            log.warning('validation error: %s ' % e)
            status = 99

        if status == 1:
            log.info('Playlist "%s" checked - all fine!' % (self.name))

        return status, messages

    ###################################################################
    # playlist mixdown
    ###################################################################
    def get_mixdown(self):
        """
        get mixdown from api
        """
        return MixdownAPIClient().get_for_playlist(self)

    def request_mixdown(self):
        """
        request (re-)creation of mixdown
        """
        return MixdownAPIClient().request_for_playlist(self)

    def download_mixdown(self):
        """
        download generated mixdown from api & store locally (in `mixdown_file` field)
        """
        if not self.mixdown:
            log.info('mixdown not available on api')
            return

        if not self.mixdown['status'] == 3:
            log.info('mixdown not ready on api')
            return

        url = self.mixdown['mixdown_file']

        log.debug('download mixdown from api: {} > {}'.format(url, self.name))

        f_temp = NamedTemporaryFile(delete=True)
        f_temp.write(urlopen(url).read())
        f_temp.flush()

        # wipe existing file
        try:
            self.mixdown_file.delete(False)
        except IOError:
            pass

        self.mixdown_file.save(url.split('/')[-1], File(f_temp))

        return MixdownAPIClient().request_for_playlist(self)

    @property
    def sorted_items(self):
        return self.items.order_by('playlist_items__position')

    @cached_property
    def num_media(self):
        return self.items.count()

    @cached_property
    def mixdown(self):
        return self.get_mixdown()

    # provide type-based properties
    @property
    def is_broadcast(self):
        return self.type == Playlist.TYPE_BROADCAST

    @property
    def is_playlist(self):
        return self.type == Playlist.TYPE_PLAYLIST

    @cached_property
    def is_archived(self):
        if not self.type == Playlist.TYPE_BROADCAST:
            return
        if self.rotation_date_end and self.rotation_date_end < timezone.now(
        ).date():
            return True

    @cached_property
    def is_upcoming(self):
        if not self.type == Playlist.TYPE_BROADCAST:
            return
        if self.rotation_date_start and self.rotation_date_start > timezone.now(
        ).date():
            return True

    @cached_property
    def series_display(self):
        if not self.series:
            return
        if self.series_number:
            return '{} #{}'.format(self.series.name, self.series_number)
        return self.series.name

    @cached_property
    def last_emission(self):
        ###############################################################
        # we cannot filter a prefetched qs - so to avoid
        # additional queries we have to loop the qs and 'filter'
        # 'manually'
        ###############################################################
        for emission in self.emissions.order_by('-time_start'):
            if emission.time_start < timezone.now():
                return emission

    @cached_property
    def next_emission(self):
        ###############################################################
        # we cannot filter a prefetched qs - so to avoid
        # additional queries we have to loop the qs and 'filter'
        # 'manually'
        ###############################################################
        for emission in self.emissions.order_by('time_start'):
            if emission.time_start > timezone.now():
                return emission

    def save(self, *args, **kwargs):

        # status update
        if self.status == 0:
            self.status = 2

        duration = 0
        try:
            duration = self.get_duration()

        except Exception as e:
            pass

        self.duration = duration

        # TODO: maybe move
        self.broadcast_status, self.broadcast_status_messages = self.self_check(
        )
        if self.broadcast_status == 1:
            self.status = 1  # 'ready'
        else:
            self.status = 99  # 'error'

        super(Playlist, self).save(*args, **kwargs)
示例#4
0
文件: models.py 项目: vince0656/web
class Grant(SuperModel):
    """Define the structure of a Grant."""
    class Meta:
        """Define the metadata for Grant."""

        ordering = ['-created_on']

    GRANT_TYPES = [('tech', 'tech'), ('media', 'media')]

    active = models.BooleanField(
        default=True, help_text=_('Whether or not the Grant is active.'))
    grant_type = models.CharField(max_length=15,
                                  choices=GRANT_TYPES,
                                  default='tech',
                                  help_text=_('Grant CLR category'))
    title = models.CharField(default='',
                             max_length=255,
                             help_text=_('The title of the Grant.'))
    slug = AutoSlugField(populate_from='title')
    description = models.TextField(
        default='', blank=True, help_text=_('The description of the Grant.'))
    description_rich = models.TextField(default='',
                                        blank=True,
                                        help_text=_('HTML rich description.'))
    reference_url = models.URLField(
        blank=True, help_text=_('The associated reference URL of the Grant.'))
    link_to_new_grant = models.ForeignKey(
        'grants.Grant',
        null=True,
        on_delete=models.SET_NULL,
        help_text=_('Link to new grant if migrated'))
    logo = models.ImageField(
        upload_to=get_upload_filename,
        null=True,
        blank=True,
        help_text=_('The Grant logo image.'),
    )
    logo_svg = models.FileField(
        upload_to=get_upload_filename,
        null=True,
        blank=True,
        help_text=_('The Grant logo SVG.'),
    )
    admin_address = models.CharField(
        max_length=255,
        default='0x0',
        help_text=_(
            'The wallet address where subscription funds will be sent.'),
    )
    contract_owner_address = models.CharField(
        max_length=255,
        default='0x0',
        help_text=
        _('The wallet address that owns the subscription contract and is able to call endContract()'
          ),
    )
    amount_goal = models.DecimalField(
        default=1,
        decimal_places=4,
        max_digits=50,
        help_text=_(
            'The monthly contribution goal amount for the Grant in DAI.'),
    )
    monthly_amount_subscribed = models.DecimalField(
        default=0,
        decimal_places=4,
        max_digits=50,
        help_text=_('The monthly subscribed to by contributors USDT/DAI.'),
    )
    amount_received = models.DecimalField(
        default=0,
        decimal_places=4,
        max_digits=50,
        help_text=_('The total amount received for the Grant in USDT/DAI.'),
    )
    token_address = models.CharField(
        max_length=255,
        default='0x0',
        help_text=_('The token address to be used with the Grant.'),
    )
    token_symbol = models.CharField(
        max_length=255,
        default='',
        help_text=_('The token symbol to be used with the Grant.'),
    )
    contract_address = models.CharField(
        max_length=255,
        default='0x0',
        help_text=_('The contract address of the Grant.'),
    )
    deploy_tx_id = models.CharField(
        max_length=255,
        default='0x0',
        help_text=_('The transaction id for contract deployment.'),
    )
    cancel_tx_id = models.CharField(
        max_length=255,
        default='0x0',
        help_text=_('The transaction id for endContract.'),
        blank=True,
    )
    contract_version = models.DecimalField(
        default=0,
        decimal_places=0,
        max_digits=3,
        help_text=_('The contract version the Grant.'),
    )
    metadata = JSONField(
        default=dict,
        blank=True,
        help_text=
        _('The Grant metadata. Includes creation and last synced block numbers.'
          ),
    )
    network = models.CharField(
        max_length=8,
        default='mainnet',
        help_text=_('The network in which the Grant contract resides.'),
    )
    required_gas_price = models.DecimalField(
        default='0',
        decimal_places=0,
        max_digits=50,
        help_text=_('The required gas price for the Grant.'),
    )
    admin_profile = models.ForeignKey(
        'dashboard.Profile',
        related_name='grant_admin',
        on_delete=models.CASCADE,
        help_text=_('The Grant administrator\'s profile.'),
        null=True,
    )
    team_members = models.ManyToManyField(
        'dashboard.Profile',
        related_name='grant_teams',
        help_text=_('The team members contributing to this Grant.'),
    )
    image_css = models.CharField(
        default='',
        blank=True,
        max_length=255,
        help_text=_('additional CSS to attach to the grant-banner img.'))
    clr_matching = models.DecimalField(
        default=0,
        decimal_places=2,
        max_digits=20,
        help_text=_('The TOTAL CLR matching amount across all rounds'),
    )
    clr_prediction_curve = ArrayField(
        ArrayField(
            models.FloatField(),
            size=2,
        ),
        blank=True,
        default=list,
        help_text=_('5 point curve to predict CLR donations.'))
    activeSubscriptions = ArrayField(models.CharField(max_length=200),
                                     blank=True,
                                     default=list)
    hidden = models.BooleanField(
        default=False, help_text=_('Hide the grant from the /grants page?'))
    weighted_shuffle = models.PositiveIntegerField(blank=True, null=True)
    contribution_count = models.PositiveIntegerField(blank=True, default=0)
    contributor_count = models.PositiveIntegerField(blank=True, default=0)
    defer_clr_to = models.ForeignKey(
        'grants.Grant',
        related_name='defered_clr_from',
        on_delete=models.CASCADE,
        help_text=_(
            'The Grant that this grant defers it CLR contributions to (if any).'
        ),
        null=True,
    )
    last_clr_calc_date = models.DateTimeField(
        help_text=_('The last clr calculation date'),
        null=True,
        blank=True,
    )
    next_clr_calc_date = models.DateTimeField(
        help_text=_('The last clr calculation date'),
        null=True,
        blank=True,
    )
    categories = models.ManyToManyField(GrantCategory)

    # Grant Query Set used as manager.
    objects = GrantQuerySet.as_manager()

    def __str__(self):
        """Return the string representation of a Grant."""
        return f"id: {self.pk}, active: {self.active}, title: {self.title}, type: {self.grant_type}"

    def percentage_done(self):
        """Return the percentage of token received based on the token goal."""
        if not self.amount_goal:
            return 0
        return ((float(self.amount_received_with_phantom_funds) /
                 float(self.amount_goal)) * 100)

    def updateActiveSubscriptions(self):
        """updates the active subscriptions list"""
        handles = []
        for handle in Subscription.objects.filter(
                grant=self,
                active=True).distinct('contributor_profile').values_list(
                    'contributor_profile__handle', flat=True):
            handles.append(handle)
        self.activeSubscriptions = handles

    @property
    def org_name(self):
        from git.utils import org_name
        try:
            return org_name(self.reference_url)
        except Exception:
            return None

    @property
    def get_contribution_count(self):
        num = 0
        for sub in self.subscriptions.all():
            for contrib in sub.subscription_contribution.filter(success=True):
                num += 1
        for pf in self.phantom_funding.all():
            num += 1
        return num

    @property
    def contributors(self):
        return_me = []
        for sub in self.subscriptions.all():
            for contrib in sub.subscription_contribution.filter(success=True):
                return_me.append(contrib.subscription.contributor_profile)
        for pf in self.phantom_funding.all():
            return_me.append(pf.profile)
        return return_me

    @property
    def get_contributor_count(self):
        contributors = []
        for sub in self.subscriptions.all():
            for contrib in sub.subscription_contribution.filter(success=True):
                contributors.append(
                    contrib.subscription.contributor_profile.handle)
        for pf in self.phantom_funding.all():
            contributors.append(pf.profile.handle)
        return len(set(contributors))

    @property
    def org_profile(self):
        from dashboard.models import Profile
        profiles = Profile.objects.filter(handle__iexact=self.org_name)
        if profiles.count():
            return profiles.first()
        return None

    @property
    def history_by_month(self):
        # gets the history of contributions to this grant month over month so they can be shown o grant details
        # returns [["", "Subscription Billing",  "New Subscriptions", "One-Time Contributions", "CLR Matching Funds"], ["December 2017", 5534, 2011, 0, 0], ["January 2018", 10396, 0 , 0, 0 ], ... for each monnth in which this grant has contribution history];
        CLR_PAYOUT_HANDLES = [
            'vs77bb', 'gitcoinbot', 'notscottmoore', 'owocki'
        ]
        month_to_contribution_numbers = {}
        subs = self.subscriptions.all().prefetch_related(
            'subscription_contribution')
        for sub in subs:
            contribs = [
                sc for sc in sub.subscription_contribution.all() if sc.success
            ]
            for contrib in contribs:
                #add all contributions
                key = contrib.created_on.strftime("%Y/%m")
                subkey = 'One-Time'
                if int(contrib.subscription.num_tx_approved) > 1:
                    if contrib.is_first_in_sequence:
                        subkey = 'New-Recurring'
                    else:
                        subkey = 'Recurring-Recurring'
                if contrib.subscription.contributor_profile.handle in CLR_PAYOUT_HANDLES:
                    subkey = 'CLR'
                if key not in month_to_contribution_numbers.keys():
                    month_to_contribution_numbers[key] = {
                        "One-Time": 0,
                        "Recurring-Recurring": 0,
                        "New-Recurring": 0,
                        'CLR': 0
                    }
                if contrib.subscription.amount_per_period_usdt:
                    month_to_contribution_numbers[key][subkey] += float(
                        contrib.subscription.amount_per_period_usdt)
        for pf in self.phantom_funding.all():
            #add all phantom funds
            subkey = 'One-Time'
            key = pf.created_on.strftime("%Y/%m")
            if key not in month_to_contribution_numbers.keys():
                month_to_contribution_numbers[key] = {
                    "One-Time": 0,
                    "Recurring-Recurring": 0,
                    "New-Recurring": 0,
                    'CLR': 0
                }
            month_to_contribution_numbers[key][subkey] += float(pf.value)

        # sort and return
        return_me = [[
            "", "Subscription Billing", "New Subscriptions",
            "One-Time Contributions", "CLR Matching Funds"
        ]]
        for key, val in (sorted(month_to_contribution_numbers.items(),
                                key=lambda kv: (kv[0]))):
            return_me.append([
                key, val['Recurring-Recurring'], val['New-Recurring'],
                val['One-Time'], val['CLR']
            ])
        return return_me

    @property
    def history_by_month_max(self):
        max_amount = 0
        for ele in self.history_by_month:
            if type(ele[1]) is float:
                max_amount = max(max_amount, ele[1] + ele[2] + ele[3] + ele[4])
        return max_amount

    @property
    def amount_received_with_phantom_funds(self):
        return float(self.amount_received) + float(
            sum([ele.value for ele in self.phantom_funding.all()]))

    @property
    def abi(self):
        """Return grants abi."""
        if self.contract_version == 0:
            from grants.abi import abi_v0
            return abi_v0
        elif self.contract_version == 1:
            from grants.abi import abi_v1
            return abi_v1

    @property
    def url(self):
        """Return grants url."""
        from django.urls import reverse
        return reverse('grants:details',
                       kwargs={
                           'grant_id': self.pk,
                           'grant_slug': self.slug
                       })

    def get_absolute_url(self):
        return self.url

    @property
    def contract(self):
        """Return grants contract."""
        from dashboard.utils import get_web3
        web3 = get_web3(self.network)
        grant_contract = web3.eth.contract(Web3.toChecksumAddress(
            self.contract_address),
                                           abi=self.abi)
        return grant_contract
示例#5
0
class Channel(BaseModel):

    name = models.CharField(max_length=256, null=True, blank=True)
    teaser = models.CharField(max_length=512, null=True, blank=True)
    slug = AutoSlugField(populate_from='name')
    
    TYPE_CHOICES = (
        ('stream', _('Stream')),
        ('djmon', _('DJ-Monitor')),
    )
    type = models.CharField(verbose_name=_('Type'), max_length=12, default='stream', choices=TYPE_CHOICES)
    
    stream_url = models.CharField(max_length=256, null=True, blank=True, help_text=_('setting the stream-url overrides server settings'))
    description = extra.MarkdownTextField(blank=True, null=True)
    station = models.ForeignKey('Station', null=True, blank=True, on_delete=models.SET_NULL)
    rtmp_app = models.CharField(max_length=256, null=True, blank=True)
    rtmp_path = models.CharField(max_length=256, null=True, blank=True)
    has_scheduler = models.BooleanField(default=False)
    mount = models.CharField(max_length=64, null=True, blank=True)

    # credentials for tunein api
    tunein_station_id = models.CharField(max_length=16, null=True, blank=True)
    tunein_partner_id = models.CharField(max_length=16, null=True, blank=True)
    tunein_partner_key = models.CharField(max_length=16, null=True, blank=True)

    # credentials for icecast2 metadata
    icecast2_server = models.CharField(max_length=256, null=True, blank=True)
    icecast2_mountpoint = models.CharField(max_length=128, null=True, blank=True)
    icecast2_admin_user = models.CharField(max_length=128, null=True, blank=True)
    icecast2_admin_pass = models.CharField(max_length=128, null=True, blank=True)

    on_air_type = models.ForeignKey(ContentType, null=True, blank=True)
    on_air_id = models.PositiveIntegerField(null=True, blank=True)
    on_air = GenericForeignKey('on_air_type', 'on_air_id')

    class Meta:
        app_label = 'abcast'
        verbose_name = _('Channel')
        verbose_name_plural = _('Channels')
        ordering = ('name', )
        unique_together = ('on_air_type', 'on_air_id')

    def __unicode__(self):
        return "%s" % self.name


    def get_absolute_url(self):
        return reverse('abcast-station-detail', kwargs={
            'slug': self.station.slug
        })
    
    def get_api_url(self):
        return reverse('api_dispatch_detail', kwargs={  
            'api_name': 'v1',  
            'resource_name': 'abcast/channel',  
            'pk': self.pk  
        }) + ''

    def get_dayparts(self, day):
        dayparts = []
        daypart_sets = self.daypartsets.filter(time_start__lte=day, time_end__gte=day, channel=self)
        daypart_set = None
        if daypart_sets.count() > 0:
            daypart_set = daypart_sets[0]

        if daypart_set:
            for dp in daypart_set.daypart_set.all():
                dayparts.append(dp)
        
        return dayparts


    def get_on_air(self):
        """
        merge currently playing item (told by pypo) with estimated scheduler entry for the emission
        """
        now = datetime.datetime.now()
        emissions = self.scheduler_emissions.filter(channel__pk=self.pk, time_start__lte=now, time_end__gte=now)
        if emissions.count() > 0:

            emission = emissions[0]
            emission_url = emissions[0].get_api_url()

            emission_items = []
            """
            for e in emission.get_timestamped_media():
                item = e.content_object
                emission_items.append({
                    'pk': item.pk,
                    'time_start': e.timestamp,
                    'resource_uri': item.get_api_url()
                })
            """

        else:
            emission_url = None
            emission_items = []

        try:
            item_url = self.on_air.get_api_url()
        except:
            item_url = None

        on_air = {
            'item': item_url,
            'emission': emission_url,
            'emission_items': emission_items
        }

        return on_air
示例#6
0
class Suggester(CrawledItem):
    """Submittter of a suggestion."""
    slug = AutoSlugField(populate_from='title', max_length=120)
    title = models.TextField()
示例#7
0
class Refinement(models.Model):
    name = models.CharField(max_length=50)
    xsd_name = models.CharField(max_length=50, default='')
    slug = AutoSlugField(max_length=50, overwrite=True, populate_from='name')
    # Cannot use a ReferenceField to template: not same database. Use the template hash instead.
    template_hash = models.CharField(max_length=255)

    def __unicode__(self):
        return self.name

    @staticmethod
    def get_all():
        """ Get all refinements.

        Returns: Refinement collection

        """
        return Refinement.objects.all()

    @staticmethod
    def get_all_filtered_by_template_hash(template_hash):
        """ Get all refinements by template hash.

        Args:
            template_hash:

        Returns: Refinement collection

        """
        return Refinement.objects.all().filter(template_hash=template_hash)

    @staticmethod
    def create_and_save(name, xsd_name, template_hash):
        """ Create and save a refinement.

        Args:
            name:
            xsd_name:
            template_hash:

        Returns:

        """
        return Refinement.objects.create(name=name,
                                         xsd_name=xsd_name,
                                         template_hash=template_hash)

    @staticmethod
    def check_refinements_already_exist_by_template_hash(template_hash):
        """ Check if the refinements have already been generated for the template.

        Args:
            template_hash:

        Returns:
            Boolean: True/False

        """
        return len(
            Refinement.get_all_filtered_by_template_hash(template_hash)) > 0

    @staticmethod
    def get_by_template_hash_and_by_slug(template_hash, slug):
        """ Get refinement by template hash and by slug.

        Args:
            template_hash:
            slug:

        Returns: Refinement collection

        """
        try:
            return Refinement.objects.get(template_hash=template_hash,
                                          slug__startswith=slug)
        except Refinement.DoesNotExist as e:
            raise exceptions.DoesNotExist(e.message)
        except Exception as ex:
            raise exceptions.ModelError(ex.message)
示例#8
0
class CaseList(models.Model):
    OBSERVER = 'O'
    CASEREPORT = 'C'
    EXAMINATION = 'E'
    SHOWCASE = 'S'
    caselist_type_choices = (
        (OBSERVER, 'Observer variability'),
        (CASEREPORT, 'Case reporting'),
        (EXAMINATION, 'Examination'),
        (SHOWCASE, 'Show case'),
    )
    Name = models.CharField(max_length=50)
    Slug = AutoSlugField(populate_from='Name', unique=True)
    Abstract = models.TextField()  # This can be entered as markdown
    Description = models.TextField()  # This can be entered as markdown
    InviteMail = models.TextField()
    WelcomeMail = models.TextField()
    ReminderMail = models.TextField()
    Type = models.CharField(max_length=1, choices=caselist_type_choices)
    ObserversPerCase = models.PositiveIntegerField(
        default=0, help_text='Enter 0 to have all cases seen by observers')
    Owner = models.ForeignKey(settings.AUTH_USER_MODEL,
                              on_delete=models.PROTECT,
                              related_name="Owner")
    VisibleForNonUsers = models.BooleanField(
        help_text=
        'Case shows up on main screen when a user is not assigned to this case list',
        default=False)
    OpenForRegistration = models.BooleanField(
        help_text='A user can admit himself to this case list', default=False)
    SelfRegistration = models.BooleanField(
        help_text=
        'If false, the owner must accept the admittance of a user to the case list',
        default=False)
    StartDate = models.DateTimeField(default=timezone.now)
    EndDate = models.DateTimeField(default=default_enddate, blank=True)
    Status = models.CharField(max_length=10, blank=True)
    Users = models.ManyToManyField(User, through='UserCaseList')
    SlideBase = models.CharField(max_length=200, blank=True)

    def is_active(self):
        currentdate = datetime.now(timezone.utc)
        return currentdate > self.StartDate and currentdate < self.EndDate

    def cases(self):
        # get a set of case ids for this caselist
        return set(
            Case.objects.filter(Caselist=self).values_list('pk', flat=True))

    def case_count(self):
        return len(self.cases())

    def user_count(self):
        return UserCaseList.objects.filter(CaseList=self).count()

    def cases_total(self, user_id):
        return self.cases().difference(self.cases_skipped(user_id))

    def case_count_total(self, user_id):
        return len(self.cases_total(user_id))

    def cases_completed(self, user_id):
        # get a set of case ids that a user has completed
        user = User.objects.get(pk=user_id)
        return set(
            CaseInstance.objects.filter(User=user,
                                        Status='E',
                                        Case__in=self.cases()).values_list(
                                            'Case', flat=True))

    def case_count_completed(self, user_id):
        return len(self.cases_completed(user_id))

    def cases_skipped(self, user_id):
        # get a set of case ids that a user has completed
        user = User.objects.get(pk=user_id)
        return set(
            CaseInstance.objects.filter(User=user,
                                        Status='S',
                                        Case__in=self.cases()).values_list(
                                            'Case', flat=True))

    def case_count_skipped(self, user_id):
        return len(self.cases_skipped(user_id))

    def cases_todo(self, user_id):
        return self.cases().difference(
            self.cases_completed(user_id)).difference(
                self.cases_skipped(user_id))

    def get_next_case(self, user_id):
        # Select a case that has not been scored yet
        if self.ObserversPerCase == 0:
            # Select a list of cases with the lowest Order value still to be scored
            cases = Case.objects.filter(Caselist=self).exclude(
                caseinstance__User=user_id).order_by('Order')
            if cases:
                cases = cases.filter(Order=cases[0].Order)
                if self.Type == self.OBSERVER:
                    return choice(list(cases.values_list('pk', flat=True)))
                else:
                    return cases[0].pk
            else:
                return -1
        else:
            # Limit number of assessments per case
            # count the number of complete caseinstance for each case
            cases = Case.objects.filter(Caselist=self).\
                exclude(caseinstance__User=user_id).annotate(models.Count('caseinstance')).\
                order_by('caseinstance__count')
            if cases:
                # Select from the cases that have been assessed least
                cases = cases.filter(
                    caseinstance__count=cases[0].caseinstance__count)
                return choice(list(cases.values_list('pk', flat=True)))
            else:
                return -1

    def evaluation(self, user_id):
        user = User.objects.get(pk=user_id)
        caseinstances = CaseInstance.objects.filter(
            User=user, Status='E',
            Case__in=self.cases()).values_list('Case', flat=True)
        answers = Answer.objects.filter(
            CaseInstance__in=caseinstances.values('id'))
        graded_answers = 0
        correct_answers = 0
        for answer in answers:
            grade = answer.grade()
            if grade == Answer.ERROR or grade == Answer.CORRECT:
                graded_answers += 1
                if grade == Answer.CORRECT:
                    correct_answers += 1
        if graded_answers > 0:
            return f'{correct_answers} of {graded_answers}'
        else:
            return ''

    def __str__(self):
        return self.Name
示例#9
0
class MailMessage(models.Model):
    email_from = models.EmailField(max_length=256)
    reply_to = models.EmailField(blank=True, null=True)
    to = models.ManyToManyField(EmailAddress,
                                blank=True,
                                null=True,
                                related_name='message_to')
    cc = models.ManyToManyField(EmailAddress,
                                blank=True,
                                null=True,
                                related_name='message_cc')
    bcc = models.ManyToManyField(EmailAddress,
                                 blank=True,
                                 null=True,
                                 related_name='message_bcc')
    body = models.TextField(blank=True, null=True)
    subject = models.CharField(max_length=1024)
    attachments = models.ManyToManyField(Attachment,
                                         blank=True,
                                         null=True,
                                         related_name='message_attachments')
    request = models.ForeignKey(Request, blank=True, null=True)
    replies = models.ManyToManyField("self",
                                     null=True,
                                     blank=True,
                                     related_name='prior_thread')
    #if it is a reply, then time it was received by mail server otherwise NOW
    dated = models.DateTimeField(null=True, blank=True)
    #when message was created on our database, if sent by us then created == dated
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    slug = AutoSlugField(populate_from=('subject', ), overwrite=False)
    direction = models.CharField(
        max_length=1,
        choices=MSG_DIRECTIONS,
    )
    message_id = models.CharField(max_length=255, blank=True, null=True)
    #todo move to using the MessageId class
    received_header = models.TextField(null=True, blank=True)
    references = models.ManyToManyField(MessageId,
                                        blank=True,
                                        null=True,
                                        related_name='message_references')
    deprecated = models.DateTimeField(null=True)
    was_fwded = models.BooleanField(
        'this message was sent by a user to this thread', default=False)

    objects = MailManager()

    @staticmethod
    def get_notes():
        #message id is set by mailgun / mail server so unsent messages or user notes have no id
        return MailMessage.objects.filter(message_id__isnull=True)

    def get_mailboxes(self):
        return self.mailbox_messages.all()

    def get_email_addresses(self):
        retval = []
        retval = retval + [address.get_email for address in self.to.all()]
        retval = retval + [address.get_email for address in self.cc.all()]
        retval = retval + [address.get_email for address in self.bcc.all()]
        retval = retval + [self.email_from]
        retval = retval + [self.reply_to]
        retval = [address for address in retval
                  if address]  # Remove any 'Nones'
        return retval

    @property
    def get_references_hdr(self):
        retval = [reference.get_msg_id for reference in self.references.all()]
        return "\t".join(retval)

    @property
    def get_reference_ids(self):
        return [reference.get_msg_id for reference in self.references.all()]

    def add_references(self, references):
        for reference in references:
            self.references.add(reference)

    @property
    def get_from_email(self):
        return self.email_from

    @property
    def get_to_emails(self):
        return [to.content for to in self.to.all()]

    @property
    def get_cc_emails(self):
        return [cc.content for cc in self.cc.all()]

    @property
    def plain_text_body(self):
        from HTMLParser import HTMLParser

        class MLStripper(HTMLParser):
            def __init__(self):
                self.reset()
                self.fed = []

            def handle_data(self, d):
                self.fed.append(d)

            def get_data(self):
                return ''.join(self.fed)

        s = MLStripper()
        s.feed(self.body)
        return s.get_data()

    @property
    def get_body(self):
        return self.body.replace("\r", "<br/>")

    def send(self, provisioned_address, references=None):
        data = {
            "from":
            self.email_from,
            "to": [addr.get_email for addr in self.to.all()] +
            ([provisioned_address] if hasattr(settings, 'MG_ROUTE') else []),
            "subject":
            self.subject,
            #"h:reply-to": self.reply_to,
            "html":
            self.body
        }
        if references is not None:
            data['h:References'] = references
        if self.bcc.all().count() > 0:
            data['bcc'] = [addr.get_email for addr in self.bcc.all()]
        if self.cc.all().count() > 0:
            data['cc'] = [addr.get_email for addr in self.cc.all()]

        resp = requests.post(settings.MG_POST_URL,
                             files=MultiDict([
                                 ("attachment", attachment.file)
                                 for attachment in self.attachments.all()
                             ]),
                             auth=("api", settings.MAILGUN_KEY),
                             data=data)
        content = json.loads(resp.content)
        self.dated = timezone.now()
        logging.info('MESSAGE SEND STATUS:%s' % resp.content)
        retval = None
        if "id" in content.keys():
            logger.info("SENT MESSAGE message_id=%s" % content['id'])
            self.message_id = content['id']
            retval = content['id']
        self.save()
        return retval
    def test_populate_from_does_not_allow_bytes(self):
        with pytest.raises(TypeError):
            AutoSlugField(populate_from=b'bytes')

        with pytest.raises(TypeError):
            AutoSlugField(populate_from=[b'bytes'])
 def test_populate_from_must_allow_string_or_list_str_or_tuple_str(self):
     AutoSlugField(populate_from='str')
     AutoSlugField(populate_from=['str'])
     AutoSlugField(populate_from=('str', ))
示例#12
0
class Tag(models.Model):
    name = AutoSlugField(unique=True, max_length=30, populate_from=['name'])

    def __str__(self):
        return self.name
示例#13
0
class Recipe(models.Model):
    """
    Django Model to hold Recipes.

    Courses have a one to Many relation with Recipes.
    Cuisines have a one to Many relation with Recipes.
    Tags have a Many to Many relation with Recipes.
    Ingredient Groups have a Many to one relation with Recipes.
    Subrecipes have a Many to Many relation with Recipes. 
        They allow another recipe to be show in the Ingredient section.

    :title: = Title of the Recipe
    :author: = Creator of the Recipe
    :photo: = Raw Image of a Recipe
    :photo_thumbnail: = compressed image of the photo
    :info: = Description of the recipe
    :directions: = How to make the recipe
    :prep_time: = How long it takes to prepare the recipe
    :cook_time: = How long the recipe takes to cook
    :servings: = How many people the recipe with serve
    :rating: = Rating of the recipe
    :pub_date: = When the recipe was created
    :update_date: = When the recipe was updated
    """
    title = models.CharField(_("Recipe Title"), max_length=250)
    slug = AutoSlugField(_('slug'), populate_from='title', unique=True)
    author = models.ForeignKey(User, verbose_name=_('user'), null=True)
    photo = models.ImageField(_('photo'),
                              blank=True,
                              upload_to="upload/recipe_photos")
    photo_thumbnail = ImageSpecField(source='photo',
                                     processors=[ResizeToFill(300, 200)],
                                     format='JPEG',
                                     options={'quality': 70})
    cuisine = models.ForeignKey(Cuisine, verbose_name=_('cuisine'))
    course = models.ForeignKey(Course, verbose_name=_('course'))
    tags = models.ManyToManyField(Tag, verbose_name=_('tag'), blank=True)
    subrecipes = models.ManyToManyField('self',
                                        verbose_name=_('subrecipes'),
                                        through='SubRecipe',
                                        symmetrical=False)
    info = models.TextField(_('info'),
                            help_text="enter information about the recipe",
                            blank=True)
    directions = models.TextField(_('direction_text'),
                                  help_text="directions",
                                  blank=True)
    source = models.CharField(_('course'), max_length=200, blank=True)
    prep_time = models.IntegerField(_('prep time'),
                                    help_text="enter time in minutes")
    cook_time = models.IntegerField(_('cook time'),
                                    help_text="enter time in minutes")
    servings = models.IntegerField(_('servings'),
                                   help_text="enter total number of servings")
    rating = models.IntegerField(_('rating'),
                                 help_text="rating of the meal",
                                 default=0)
    pub_date = models.DateTimeField(auto_now_add=True)
    update_date = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-pub_date', 'title']

    def __unicode__(self):
        return '%s' % self.title
示例#14
0
class FunctionSluggedTestModel(models.Model):
    title = models.CharField(max_length=42)
    slug = AutoSlugField(populate_from=get_readable_title)

    class Meta:
        app_label = 'django_extensions'
示例#15
0
class Group(CrawledItem):
    TYPE_GROUP = 'group'
    TYPE_COMMITTEE = 'committee'
    TYPE_COMMISSION = 'commission'
    TYPE_FRACTION = 'fraction'
    TYPE_PARLIAMENT = 'parliament'

    GROUP_TYPES = (
        (TYPE_GROUP, _('Group')),
        (TYPE_COMMITTEE, _('Committee')),
        (TYPE_COMMISSION, _('Commission')),
        (TYPE_FRACTION, _('Fraction')),
        (TYPE_PARLIAMENT, _('Parliament')),
    )

    name = models.CharField(max_length=255, unique=True)
    slug = AutoSlugField(populate_from='name', max_length=120)
    abbr = models.CharField(max_length=16, blank=True, null=True)
    type = models.CharField(max_length=64, choices=GROUP_TYPES)
    displayed = models.BooleanField(default=True)
    logo = models.ImageField(upload_to='fraction_logos', blank=True, null=True)

    # Precomputed stats fields
    avg_statement_count = models.FloatField(blank=True, null=True)
    avg_long_statement_count = models.FloatField(blank=True, null=True)
    avg_vote_percentage = models.FloatField(blank=True, null=True)
    avg_discussion_contribution_percentage = models.FloatField(blank=True,
                                                               null=True)
    positions = JSONField(default=None, blank=True, null=True)
    avg_law_project_count = models.FloatField(blank=True, null=True)
    avg_passed_law_project_count = models.FloatField(blank=True, null=True)
    avg_passed_law_project_ratio = models.FloatField(blank=True, null=True)

    precomputed_fields = (
        ('avg_statement_count', 'get_avg_statement_count'),
        ('avg_long_statement_count', 'get_avg_long_statement_count'),
        ('avg_vote_percentage', 'get_avg_vote_percentage'),
        ('avg_discussion_contribution_percentage',
         'get_avg_discussion_contribution_percentage'),
        ('positions', 'get_positions'),
        ('avg_law_project_count', 'get_avg_proposed_law_project_count'),
        ('avg_passed_law_project_count', 'get_avg_passed_law_project_count'),
        ('avg_passed_law_project_ratio', 'get_avg_passed_law_project_ratio'),
    )
    precomputation_filter = {
        'type__in': (TYPE_FRACTION, TYPE_PARLIAMENT),
    }
    precomputation_depends_on = ('ParliamentMember', )

    class Meta:
        unique_together = (('name', 'type'))

    def __unicode__(self):
        return u'{} ({})'.format(self.name, self.type)

    @property
    def active_members(self):
        return self.members.filter(groupmembership__until=None)

    @reify
    def active_member_count(self):
        return self.active_members.count()

    def get_avg_statement_count(self):
        agg = self.active_members.filter(
            statements__as_chairperson=False, ).annotate(
                models.Count('statements')).aggregate(
                    avg_statements=models.Avg('statements__count'))
        return agg['avg_statements']

    def get_avg_long_statement_count(self):
        agg = self.active_members.filter(
            statements__word_count__gte=50,
            statements__as_chairperson=False,
        ).annotate(models.Count('statements')).aggregate(
            avg_statements=models.Avg('statements__count'))
        return agg['avg_statements']

    def get_avg_vote_percentage(self):
        total_percentage = 0.0
        for member in self.active_members:
            total_percentage += member.vote_percentage
        return (total_percentage /
                self.active_member_count if self.active_member_count else 0.0)

    def get_avg_discussion_contribution_percentage(self):
        agg = self.active_members.aggregate(
            avg_contrib=models.Avg('discussion_contribution_percentage'))
        return agg['avg_contrib']

    def get_positions(self):
        from manoseimas.compatibility_test import services
        positions = services.get_topic_positions(
        )  # XXX: parliament term should be passed here
        return positions['fractions'].get(self.pk, {})

    def get_avg_proposed_law_project_count(self):
        agg = self.active_members.annotate(
            models.Count('law_projects')).aggregate(
                avg_law_projects=models.Avg('law_projects__count'))
        return agg['avg_law_projects']

    def get_avg_passed_law_project_count(self):
        agg = self.active_members.filter(
            law_projects__date_passed__isnull=False).annotate(
                models.Count('law_projects')).aggregate(
                    avg_passed_projects=models.Avg('law_projects__count'))
        return agg['avg_passed_projects']

    def get_avg_passed_law_project_ratio(self):
        agg = self.active_members.aggregate(
            avg_passed_ratio=models.Avg('passed_law_project_ratio'))
        return agg['avg_passed_ratio']

    def get_collaborating_fractions_percentage(self, count=5):

        member_projects = LawProject.objects.filter(
            proposers__in=self.active_members)
        # Retrieve proposer counts for each pair of (project_id, group_id)
        # for all fraction member projects
        project_signatories = LawProject.proposers.through.objects.filter(
            parliamentmember__groups__type=Group.TYPE_FRACTION,
            parliamentmember__groupmembership__until__isnull=True,
            lawproject__id__in=list(
                member_projects.values_list('id', flat=True).distinct()),
        ).values(
            'lawproject__id',
            'parliamentmember__groups__id',
        ).annotate(group_proposer_count=models.Count('pk'))

        project_signatories = list(project_signatories)

        # Build a dict: project -> group_id -> count
        # for retrieved projects
        projects = defaultdict(lambda: {})
        for s in project_signatories:
            proposer_count = s['group_proposer_count']
            project_id = s['lawproject__id']
            group_id = s['parliamentmember__groups__id']
            projects[project_id][group_id] = proposer_count

        # Calculate contribution ratios for each project
        project_percentages = {}
        for project_id, fraction_signatures in projects.items():
            total_signatories = sum(fraction_signatures.values())
            signature_percentages = {
                fraction_id: float(value) / total_signatories * 100.0
                for fraction_id, value in fraction_signatures.items()
            }
            project_percentages[project_id] = signature_percentages

        # Sum up contribution ratios for each fraction
        fraction_contrib_sums = defaultdict(lambda: 0.0)
        fraction_contrib_projects = defaultdict(lambda: 0)
        for project in project_percentages.values():
            for fraction, percentage in project.items():
                if fraction != self.pk:  # Ignore own percentages
                    fraction_contrib_sums[fraction] += percentage
                    fraction_contrib_projects[fraction] += 1

        # Compute fraction average contribution ratios
        fraction_contrib = {
            key: (fraction_contrib_sums[key] / fraction_contrib_projects[key])
            for key in fraction_contrib_sums.keys()
        }

        # Take top N contributing fractions and retrieve their data
        top_signatory_pairs = sorted(
            fraction_contrib.items(),
            reverse=True,
            key=lambda v: v[1],
        )
        top_signatory_dict = dict(top_signatory_pairs[:count])
        top_signatories = list(
            Group.objects.filter(pk__in=top_signatory_dict.keys()))
        # Augment ORM objects with average contribution percentage
        for signatory in top_signatories:
            signatory.signing_percentage = top_signatory_dict[signatory.pk]
        return sorted(top_signatories,
                      key=lambda s: s.signing_percentage,
                      reverse=True)

    @property
    def top_collaborating_fractions(self):
        return self.get_collaborating_fractions_percentage()
示例#16
0
class Territorio(models.Model):
    TERRITORIO = Choices(
        ('C', 'Comune'),
        ('P', 'Provincia'),
        ('R', 'Regione'),
        ('N', 'Nazionale'),
        ('E', 'Estero'),
    )

    cod_reg = models.IntegerField(default=0,
                                  blank=True,
                                  null=True,
                                  db_index=True)
    cod_prov = models.IntegerField(default=0,
                                   blank=True,
                                   null=True,
                                   db_index=True)
    cod_com = models.IntegerField(default=0,
                                  blank=True,
                                  null=True,
                                  db_index=True)
    denominazione = models.CharField(max_length=128, db_index=True)
    denominazione_ted = models.CharField(max_length=128,
                                         blank=True,
                                         null=True,
                                         db_index=True)
    slug = AutoSlugField(populate_from='nome_per_slug',
                         max_length=256,
                         unique=True,
                         db_index=True)
    territorio = models.CharField(max_length=1,
                                  choices=TERRITORIO,
                                  db_index=True)
    geom = models.MultiPolygonField(srid=4326, null=True, blank=True)
    popolazione_totale = models.IntegerField(null=True, blank=True)
    popolazione_maschile = models.IntegerField(null=True, blank=True)
    popolazione_femminile = models.IntegerField(null=True, blank=True)

    objects = TerritorioManager()

    @property
    def nome(self):
        if self.denominazione_ted:
            return u'{} - {}'.format(self.denominazione,
                                     self.denominazione_ted)
        else:
            return u'{}'.format(self.denominazione)

    @property
    def nome_completo(self):
        if self.is_comune or self.is_provincia:
            return u'{} di {}'.format(self.get_territorio_display(), self.nome)
        elif self.is_regione:
            return u'{} {}'.format(self.get_territorio_display(), self.nome)
        else:
            return u'{}'.format(self.nome)

    @property
    def nome_con_provincia(self):
        if self.is_provincia:
            return u'{} (Provincia)'.format(self.nome)
        else:
            return u'{}'.format(self.nome)

    @property
    def nome_per_slug(self):
        return u'{} {}'.format(self.denominazione,
                               self.get_territorio_display())

    @property
    def ambito_territoriale(self):
        """
        Returns: a Region (for C,P or R), Nazionale, or Estero
        """
        if self.is_regione:
            return self.nome
        elif self.regione:
            return self.regione.nome
        else:
            return self.get_territorio_display()

    @cached_property
    def regione(self):
        if self.is_comune or self.is_provincia:
            return self.__class__.objects.regioni_by_cod[self.cod_reg]
        else:
            return None

    @cached_property
    def provincia(self):
        if self.is_comune:
            return self.__class__.objects.provincie_by_cod[self.cod_prov]
        else:
            return None

    @property
    def codice(self):
        if self.is_comune:
            return self.cod_com
        elif self.is_provincia:
            return self.cod_prov
        else:
            return self.cod_reg

    def progetti(self):
        return self.progetto_set.all()

    @property
    def n_progetti(self):
        return self.progetto_set.count()

    @property
    def code(self):
        return self.get_cod_dict().values()[0]

    def get_cod_dict(self, prefix=''):
        """
        Return a dict with {prefix}cod_{type} key initialized with correct value
        """
        if self.is_comune:
            return {'{}cod_com'.format(prefix): self.cod_com}
        elif self.is_provincia:
            return {'{}cod_prov'.format(prefix): self.cod_prov}
        elif self.is_regione:
            return {'{}cod_reg'.format(prefix): self.cod_reg}
        elif self.is_nazionale:
            return {'{}cod_reg'.format(prefix): 0}
        elif self.is_estero:
            return {'{}pk'.format(prefix): self.pk}

        raise Exception('Territorio non interrogabile {}'.format(self))

    def get_progetti_search_url(self, **kwargs):
        """
        Returns the correct search url in progetti faceted navigation.
        Can be used with optional filters:
        tema=TemaInstance
        natura=ClassificazioneAzioneInstance
        """
        search_url = reverse('progetti_search') + '?q='

        if 'tema' in kwargs:
            tema = kwargs['tema']
            search_url += '&selected_facets=tema:{}'.format(tema.codice)

        if 'natura' in kwargs:
            natura = kwargs['natura']
            search_url += '&selected_facets=natura:{}'.format(natura.codice)

        if 'programma' in kwargs:
            programma = kwargs['programma']
            search_url += '&fonte_fin={}'.format(programma.codice)

        if 'gruppo_programmi' in kwargs:
            gruppo_programmi = kwargs['gruppo_programmi']
            search_url += '&gruppo_programmi={}'.format(
                gruppo_programmi.codice)

        d = self.get_cod_dict()
        key = d.keys()[0]
        search_url += '&territorio{}={}'.format(key[3:], d[key])

        return search_url

    def get_absolute_url(self):
        url_name = 'territori_{}'.format(self.get_territorio_display().lower())

        if self.is_nazionale or self.is_estero:
            return reverse(url_name)
        else:
            return reverse(url_name, kwargs={'slug': self.slug})

    def __getattr__(self, item):
        match = re.search(
            '^is_({})$'.format('|'.join(
                dict(self.__class__.TERRITORIO).values()).lower()), item)
        if match:
            return self.get_territorio_display().lower() == match.group(1)
        else:
            raise AttributeError('{0!r} object has no attribute {1!r}'.format(
                self.__class__.__name__, item))

    def __unicode__(self):
        return u'{}'.format(self.nome)

    class Meta:
        verbose_name = u'Località'
        verbose_name_plural = u'Località'
        ordering = ['denominazione']
示例#17
0
class ParliamentMember(CrawledItem):
    slug = AutoSlugField(populate_from=('first_name', 'last_name'),
                         max_length=120)
    source_id = models.CharField(max_length=16)
    first_name = models.CharField(max_length=128)
    last_name = models.CharField(max_length=128)
    date_of_birth = models.CharField(max_length=16, blank=True, null=True)
    email = models.EmailField(blank=True, null=True)
    phone = models.CharField(max_length=32, blank=True, null=True)
    candidate_page = models.URLField(blank=True, null=True)
    raised_by = models.ForeignKey('PoliticalParty', blank=True, null=True)
    photo = models.ImageField(upload_to='profile_images',
                              blank=True,
                              null=True)
    term_of_office = models.CharField(max_length=32, blank=True, null=True)

    office_address = models.TextField(blank=True, null=True)
    constituency = models.CharField(max_length=128, blank=True, null=True)
    party_candidate = models.BooleanField(default=True)
    groups = models.ManyToManyField('Group',
                                    through='GroupMembership',
                                    related_name='members')

    biography = models.TextField(blank=True, null=True)

    # Precomputed stats fields
    statement_count = models.PositiveIntegerField(blank=True, null=True)
    long_statement_count = models.PositiveIntegerField(blank=True, null=True)
    vote_percentage = models.FloatField(blank=True, null=True)
    discussion_contribution_percentage = models.FloatField(blank=True,
                                                           null=True)
    positions = JSONField(default=None, blank=True, null=True)
    proposed_law_project_count = models.PositiveIntegerField(blank=True,
                                                             null=True)
    passed_law_project_count = models.PositiveIntegerField(blank=True,
                                                           null=True)
    passed_law_project_ratio = models.FloatField(blank=True, null=True)

    precomputed_fields = (
        ('statement_count', 'get_statement_count'),
        ('long_statement_count', 'get_long_statement_count'),
        ('vote_percentage', 'get_vote_percentage'),
        ('discussion_contribution_percentage',
         'get_discussion_contribution_percentage'),
        ('positions', 'get_positions'),
        ('proposed_law_project_count', 'get_proposed_law_project_count'),
        ('passed_law_project_count', 'get_passed_law_project_count'),
        ('passed_law_project_ratio', 'get_passed_law_project_ratio'),
    )
    precomputation_depends_on = ('StenogramStatement', )

    @property
    def full_name(self):
        return u' '.join([self.first_name, self.last_name])

    def __unicode__(self):
        return self.full_name

    def get_absolute_url(self):
        return reverse('mp_profile', kwargs={'mp_slug': self.slug})

    @property
    def fractions(self):
        return self.groups.filter(type=Group.TYPE_FRACTION)

    @property
    def fraction(self):
        ''' Current parliamentarian's fraction. '''
        if getattr(self, '_fraction', None):
            return self._fraction[0].group
        else:
            membership = GroupMembership.objects.filter(
                member=self, group__type=Group.TYPE_FRACTION, until=None)[:]

            if membership:
                self._fraction = membership
                return membership[0].group
            else:
                return None

    def get_statement_count(self):
        return self.statements.filter(as_chairperson=False).count()

    def get_long_statement_count(self):
        return self.statements.filter(as_chairperson=False).\
            filter(word_count__gte=50).count()

    def get_long_statement_percentage(self):
        statements = self.get_statement_count()
        long_statements = self.get_long_statement_count()
        return (float(long_statements) / statements *
                100 if statements else 0.0)

    def get_discussion_contribution_percentage(self):
        all_discussions = StenogramTopic.objects.count()
        contributed_discusions = StenogramStatement.objects.\
            filter(speaker=self, as_chairperson=False).\
            aggregate(topics=models.Count('topic_id',
                                          distinct=True))
        return (float(contributed_discusions['topics']) / all_discussions *
                100.0) if all_discussions else 0.0

    @property
    def votes(self):
        return get_mp_votes(self.source_id).count()

    def get_vote_percentage(self):
        # Get total votes during the time MP was in fractions
        fraction_memberships = GroupMembership.objects.filter(
            member=self, group__type=Group.TYPE_FRACTION)
        total_votes = 0
        for membership in fraction_memberships:
            start_date = membership.since
            end_date = (membership.until
                        if membership.until else datetime.date.today())
            total_votes += (scrapy_models.Voting.objects.filter(
                timestamp__range=(start_date, end_date)).count())
        if total_votes:
            vote_percentage = float(self.votes) / total_votes * 100.0
        else:
            vote_percentage = 0.0
        return vote_percentage

    def get_proposed_law_project_count(self):
        return self.law_projects.count()

    def get_passed_law_project_count(self):
        return self.law_projects.filter(date_passed__isnull=False).count()

    def get_passed_law_project_ratio(self):
        avg_passing_time = LawProject.get_avg_passing_time()
        proposed_count = self.get_proposed_law_project_count()
        proposed_count = self.law_projects.exclude(
            date_passed__isnull=True,
            date__gt=models.Func(models.Func(function='CURDATE'),
                                 models.Func(
                                     avg_passing_time,
                                     template='INTERVAL %(expressions)s DAY'),
                                 function='DATE_SUB')).count()
        if proposed_count:
            return (float(self.get_passed_law_project_count()) /
                    proposed_count * 100.0)
        else:
            return 0.0

    def get_positions(self):
        from manoseimas.compatibility_test import services
        term = services.get_term_range(self.term_of_office)
        positions = services.get_topic_positions(term)
        return positions['mps'].get(self.pk, {})

    def get_collaborators_qs(self):
        collaborators = ParliamentMember.objects.filter(
            law_projects__in=self.law_projects.all()).exclude(pk=self.pk)
        return collaborators

    def get_top_collaborators(self, count=5):
        collaborators_qs = self.get_collaborators_qs()
        collaborators = collaborators_qs.annotate(project_count=models.Count(
            '*')).distinct().order_by('-project_count')[:count]
        return collaborators

    @property
    def top_collaborators(self):
        return self.get_top_collaborators()

    @property
    def all_statements(self):
        return self.statements.all()

    @classmethod
    def FractionPrefetch(cls):
        return models.Prefetch(
            'groupmembership',
            queryset=GroupMembership.objects.select_related('group').filter(
                until=None,
                group__type=Group.TYPE_FRACTION,
                group__displayed=True),
            to_attr='_fraction')
示例#18
0
class Category(models.Model):
    HANDLING_A3DMC = 'A3DMC'
    HANDLING_A3DEC = 'A3DEC'
    HANDLING_A3WMC = 'A3WMC'
    HANDLING_A3WEC = 'A3WEC'
    HANDLING_I5DMC = 'I5DMC'
    HANDLING_STOPEC = 'STOPEC'
    HANDLING_STOPEC3 = 'STOPEC3'
    HANDLING_KLOKLICHTZC = 'KLOKLICHTZC'
    HANDLING_GLADZC = 'GLADZC'
    HANDLING_A3DEVOMC = 'A3DEVOMC'
    HANDLING_WS1EC = 'WS1EC'
    HANDLING_WS2EC = 'WS2EC'
    HANDLING_WS3EC = 'WS3EC'
    HANDLING_OND = 'ONDERMIJNING'
    HANDLING_REST = 'REST'
    HANDLING_EMPTY = 'EMPTY'
    HANDLING_LIGHTING = 'LIGHTING'
    HANDLING_GLAD_OLIE = 'GLAD_OLIE'
    HANDLING_TECHNISCHE_STORING = 'TECHNISCHE_STORING'
    HANDLING_URGENTE_MELDINGEN = 'URGENTE_MELDINGEN'
    HANDLING_3WGM = '3WGM'
    HANDLING_CHOICES = (
        (HANDLING_A3DMC, HANDLING_A3DMC),
        (HANDLING_A3DEC, HANDLING_A3DEC),
        (HANDLING_A3WMC, HANDLING_A3WMC),
        (HANDLING_A3WEC, HANDLING_A3WEC),
        (HANDLING_I5DMC, HANDLING_I5DMC),
        (HANDLING_STOPEC, HANDLING_STOPEC),
        (HANDLING_KLOKLICHTZC, HANDLING_KLOKLICHTZC),
        (HANDLING_GLADZC, HANDLING_GLADZC),
        (HANDLING_A3DEVOMC, HANDLING_A3DEVOMC),
        (HANDLING_WS1EC, HANDLING_WS1EC),
        (HANDLING_WS2EC, HANDLING_WS2EC),
        (HANDLING_WS3EC, HANDLING_WS3EC),
        (HANDLING_REST, HANDLING_REST),
        (HANDLING_OND, HANDLING_OND),
        (HANDLING_EMPTY, HANDLING_EMPTY),
        (HANDLING_LIGHTING, HANDLING_LIGHTING),
        (HANDLING_GLAD_OLIE, HANDLING_GLAD_OLIE),
        (HANDLING_TECHNISCHE_STORING, HANDLING_TECHNISCHE_STORING),
        (HANDLING_STOPEC3, HANDLING_STOPEC3),
        (HANDLING_URGENTE_MELDINGEN, HANDLING_URGENTE_MELDINGEN),
        (HANDLING_3WGM, HANDLING_3WGM),
    )

    parent = models.ForeignKey('signals.Category',
                               related_name='children',
                               on_delete=models.PROTECT,
                               null=True,
                               blank=True)

    # SIG-1135, the slug is auto populated using the django-extensions "AutoSlugField"
    slug = AutoSlugField(populate_from=[
        'name',
    ],
                         blank=False,
                         overwrite=False,
                         editable=False)

    name = models.CharField(max_length=255)
    handling = models.CharField(max_length=20,
                                choices=HANDLING_CHOICES,
                                default=HANDLING_REST)
    departments = models.ManyToManyField('signals.Department',
                                         through='signals.CategoryDepartment',
                                         through_fields=('category',
                                                         'department'))
    is_active = models.BooleanField(default=True)

    description = models.TextField(null=True, blank=True)

    objects = CategoryManager()

    class Meta:
        ordering = ('name', )
        unique_together = (
            'parent',
            'slug',
        )
        verbose_name_plural = 'Categories'

        permissions = (
            ('sia_can_view_all_categories',
             'View all categories (this will override the category permission based on the user/department relation)'
             ),  # noqa
            ('sia_category_read', 'Can read Categories from SIA'),
            ('sia_category_write', 'Can write Categories to SIA'),
        )

    def __str__(self):
        """String representation."""
        return '{name}{parent}'.format(
            name=self.name,
            parent=" ({})".format(self.parent.name) if self.parent else "")

    def is_parent(self):
        return self.children.exists()

    def is_child(self):
        return self.parent is not None

    def clean(self):
        super(Category, self).clean()

        if self.pk and self.slug:
            if not Category.objects.filter(id=self.pk,
                                           slug=self.slug).exists():
                raise ValidationError('Category slug cannot be changed')

        if self.is_parent() and self.is_child() or self.is_child(
        ) and self.parent.is_child():
            raise ValidationError(
                'Category hierarchy can only go one level deep')

    def save(self, *args, **kwargs):
        self.full_clean()
        super().save(*args, **kwargs)

    def is_translated(self):
        return (not self.is_active and self.translations.filter(
            new_category__is_active=True).exists())

    def translated_to(self):
        if self.is_translated():
            return self.translations.filter(
                new_category__is_active=True).order_by(
                    '-created_at').first().new_category
示例#19
0
class Media(MigrationMixin, UUIDModelMixin, TimestampedModelMixin,
            models.Model):

    STATUS_CHOICES = (
        (0, _('Init')),
        (1, _('Ready')),
        (3, _('Working')),
        (4, _('File missing')),
        (5, _('File error')),
        (99, _('Error')),
    )

    TRACKNUMBER_CHOICES = ((x, x) for x in range(1, 301))
    MEDIANUMBER_CHOICES = ((x, x) for x in range(1, 51))

    lock = models.PositiveIntegerField(default=0, editable=False)
    name = models.CharField(max_length=255, db_index=True)
    slug = AutoSlugField(populate_from='name',
                         editable=True,
                         blank=True,
                         overwrite=True)
    status = models.PositiveIntegerField(default=0, choices=STATUS_CHOICES)
    publish_date = models.DateTimeField(blank=True, null=True)
    tracknumber = models.PositiveIntegerField(verbose_name=_('Track Number'),
                                              blank=True,
                                              null=True,
                                              choices=TRACKNUMBER_CHOICES)
    opus_number = models.CharField(max_length=200, blank=True, null=True)
    medianumber = models.PositiveIntegerField(
        verbose_name=_('a.k.a. "Disc number'),
        blank=True,
        null=True,
        choices=MEDIANUMBER_CHOICES)
    mediatype = models.CharField(verbose_name=_('Type'),
                                 max_length=128,
                                 default='song',
                                 choices=MEDIATYPE_CHOICES)
    version = models.CharField(max_length=12,
                               blank=True,
                               null=True,
                               default='track',
                               choices=VERSION_CHOICES)
    description = models.TextField(
        verbose_name="Extra Description / Tracklist", blank=True, null=True)
    lyrics = models.TextField(blank=True, null=True)
    lyrics_language = LanguageField(blank=True, null=True)

    duration = models.PositiveIntegerField(verbose_name="Duration (in ms)",
                                           blank=True,
                                           null=True,
                                           editable=False)

    # relations
    release = models.ForeignKey('alibrary.Release',
                                blank=True,
                                null=True,
                                on_delete=models.SET_NULL,
                                related_name='media_release')
    artist = models.ForeignKey('alibrary.Artist',
                               blank=True,
                               null=True,
                               related_name='media_artist')

    # user relations
    owner = models.ForeignKey(settings.AUTH_USER_MODEL,
                              blank=True,
                              null=True,
                              on_delete=models.SET_NULL,
                              related_name="media_owner")
    creator = models.ForeignKey(settings.AUTH_USER_MODEL,
                                blank=True,
                                null=True,
                                on_delete=models.SET_NULL,
                                related_name="created_media")
    last_editor = models.ForeignKey(settings.AUTH_USER_MODEL,
                                    blank=True,
                                    null=True,
                                    on_delete=models.SET_NULL,
                                    related_name="media_last_editor")
    publisher = models.ForeignKey(settings.AUTH_USER_MODEL,
                                  blank=True,
                                  null=True,
                                  on_delete=models.SET_NULL,
                                  related_name="media_publisher")

    # identifiers
    isrc = models.CharField(verbose_name='ISRC',
                            max_length=12,
                            null=True,
                            blank=True)

    # relations a.k.a. links
    relations = GenericRelation(Relation)
    playlist_items = GenericRelation(PlaylistItem, object_id_field="object_id")

    # reverse generic relation for atracker events
    events = GenericRelation('atracker.Event')

    # tagging (d_tags = "display tags")
    d_tags = tagging.fields.TagField(verbose_name="Tags",
                                     max_length=1024,
                                     blank=True,
                                     null=True)

    # provide 'multi-names' for artist crediting, like: Artist X feat. Artist Y & Artist Z
    media_artists = models.ManyToManyField(
        'alibrary.Artist',
        blank=True,
        through='MediaArtists',
        related_name="credited",
    )

    # extra-artists
    extra_artists = models.ManyToManyField('alibrary.Artist',
                                           blank=True,
                                           through='MediaExtraartists')

    license = models.ForeignKey(License,
                                blank=True,
                                null=True,
                                on_delete=models.PROTECT,
                                related_name='media_license',
                                limit_choices_to={'selectable': True})

    filename = models.CharField(verbose_name=_('Filename'),
                                max_length=256,
                                blank=True,
                                null=True)
    original_filename = models.CharField(verbose_name=_('Original filename'),
                                         max_length=256,
                                         blank=True,
                                         null=True)

    folder = models.CharField(max_length=1024,
                              null=True,
                              blank=True,
                              editable=False)

    #######################################################################
    # master audio-file data
    # TODO: all version- & conversion based data will be refactored.
    # the media model should just hold information about the associated master file
    #######################################################################
    master = models.FileField(max_length=1024,
                              upload_to=upload_master_to,
                              blank=True,
                              null=True)
    master_sha1 = models.CharField(max_length=64,
                                   db_index=True,
                                   blank=True,
                                   null=True)
    master_encoding = models.CharField(max_length=16, blank=True, null=True)
    master_bitrate = models.PositiveIntegerField(verbose_name=_('Bitrate'),
                                                 blank=True,
                                                 null=True)
    master_filesize = models.PositiveIntegerField(verbose_name=_('Filesize'),
                                                  blank=True,
                                                  null=True)
    master_samplerate = models.PositiveIntegerField(
        verbose_name=_('Samplerate'), blank=True, null=True)
    master_duration = models.FloatField(verbose_name=_('Duration'),
                                        blank=True,
                                        null=True)

    #######################################################################
    # audio properties
    #######################################################################
    tempo = models.FloatField(null=True, blank=True)

    #######################################################################
    # fprint data
    #######################################################################
    fprint_ingested = models.DateTimeField(null=True, blank=True)

    objects = models.Manager()

    class Meta:
        app_label = 'alibrary'
        verbose_name = _('Track')
        verbose_name_plural = _('Tracks')
        ordering = ('medianumber', 'tracknumber', 'name')

        permissions = (
            ('play_media', 'Play Track'),
            ('downoad_media', 'Download Track'),
            ('merge_media', 'Merge Tracks'),
            ('reassign_media', 'Re-assign Tracks'),
            ('admin_media', 'Edit Track (extended)'),
            ('upload_media', 'Upload Track'),
        )

    def __unicode__(self):
        return self.name

    @property
    def duration_s(self):
        return self.get_duration(units='s')

    @property
    def duration_ms(self):
        return self.get_duration(units='ms')

    @property
    def is_lossless(self):
        if self.master_encoding and self.master_encoding.lower(
        ) in LOSSLESS_CODECS:
            return True

    @property
    def bitrate(self):
        if not self.is_lossless:
            return self.master_bitrate

    @property
    def classname(self):
        return self.__class__.__name__

    @property
    def main_image(self):
        """main image referes to release image if available"""
        if self.release:
            return self.release.main_image

    def get_lookup_providers(self):

        providers = []
        for key, name in LOOKUP_PROVIDERS:
            relations = self.relations.filter(service=key)
            relation = None
            if relations.count() == 1:
                relation = relations[0]

            providers.append({'key': key, 'name': name, 'relation': relation})

        return providers

    def get_ct(self):
        return '{}.{}'.format(self._meta.app_label,
                              self.__class__.__name__).lower()

    def get_absolute_url(self):
        return reverse('alibrary-media-detail',
                       kwargs={
                           'pk': self.pk,
                           'slug': self.slug,
                       })
        # return reverse('library:media-detail', kwargs={
        #     'pk': self.pk,
        #     'slug': self.slug,
        # })

    def get_edit_url(self):
        return reverse("alibrary-media-edit", args=(self.pk, ))

    def get_admin_url(self):
        return reverse("admin:alibrary_media_change", args=(self.pk, ))

    def get_api_url(self):
        return reverse('api_dispatch_detail',
                       kwargs={
                           'api_name': 'v1',
                           'resource_name': 'library/track',
                           'uuid': self.uuid
                       })

    # TODO: refactor to admin module
    def release_link(self):
        if self.release:
            return '<a href="%s">%s</a>' % (reverse(
                "admin:alibrary_release_change",
                args=(self.release.id, )), self.release.name)
        return None

    release_link.allow_tags = True
    release_link.short_description = "Edit"

    # TODO: depreciated
    def get_playlink(self):
        return '/api/tracks/%s/#0#replace' % self.uuid

    # TODO: depreciated
    def get_download_permissions(self):
        pass

    def generate_sha1(self):
        return sha1_by_file(self.master)

    # TODO: improve video/soundcloud handling
    @property
    def has_video(self):
        return self.relations.filter(service__in=['youtube', 'vimeo']).exists()

    @property
    def get_videos(self):
        return self.relations.filter(service__in=['youtube', 'vimeo'])

    @property
    def has_soundcloud(self):
        return self.relations.filter(service='soundcloud').exists()

    @property
    def get_soundcloud(self):
        return self.relations.filter(service='soundcloud').first()

    @property
    def emissions(self):
        from abcast.models import Emission
        playlist_qs = self.get_appearances()

        emission_qs = Emission.objects.past().filter(
            object_id__in=[i.pk for i in playlist_qs.all()],
            content_type=ContentType.objects.get_for_model(Playlist)).order_by(
                '-time_start').distinct()

        return emission_qs

    @property
    def last_emission(self):
        return self.emissions.first()

    # TODO: this is ugly - improve!
    def get_artist_display(self):

        artist_str = ''
        artists = self.get_mediaartists()
        if len(artists) > 1:
            try:
                for artist in artists:
                    if artist['join_phrase']:
                        if artist['join_phrase'] != ',':
                            artist_str += ' '
                        artist_str += '%s ' % artist['join_phrase']

                    artist_str += artist['artist'].name
            except:
                artist_str = artists[0].name
        else:
            try:
                artist_str = artists[0].name
            except:

                try:
                    artist_str = self.artist.name
                except:
                    artist_str = _('Unknown Artist')

        return artist_str

    def get_mediaartists(self):

        artists = []
        if self.media_artists.exists():
            for media_artist in self.media_mediaartist.all():
                artists.append({
                    'artist': media_artist.artist,
                    'join_phrase': media_artist.join_phrase
                })
            return artists

        return artists

    def get_master_path(self):
        try:
            return self.master.path
        except Exception as e:
            log.warning('unable to get master path for: %s' % self.name)

    def get_directory(self, absolute=False):

        if self.folder and absolute:
            return os.path.join(settings.MEDIA_ROOT, self.folder)

        elif self.folder:
            return self.folder

        else:
            log.warning('unable to get directory path for: %s - %s' %
                        (self.pk, self.name))
            return None

    def get_file(self, source, version):
        # TODO: implement...
        return self.master

    def get_playout_file(self, absolute=False):

        abs_path = self.master.path

        if not absolute:
            if settings.MEDIA_ROOT.endswith('/'):
                path = abs_path.replace(settings.MEDIA_ROOT + '/', '')
            else:
                path = abs_path.replace(settings.MEDIA_ROOT, '')

        else:
            path = abs_path

        return path

    def get_duration(self, units='ms'):

        if not self.master_duration:
            return

        if units == 'ms':
            return int(self.master_duration * 1000)

        if units == 's':
            return int(self.master_duration)

    # TODO: check usage.
    @property
    def appearances(self):
        return self.get_appearances()

    def get_appearances(self):
        # better approach:
        # Playlist.objects.filter(playlist_items__item__object_id=m.pk, playlist_items__item__content_type=ContentType.objects.get_for_model(Media))
        qs = Playlist.objects.filter(
            playlist_items__item__object_id=self.pk,
            playlist_items__item__content_type=ContentType.objects.
            get_for_model(self)).exclude(type=Playlist.TYPE_OTHER).order_by(
                '-type', '-created').nocache().distinct()

        return qs

        # ps = []
        # try:
        #     pis = PlaylistItem.objects.filter(object_id=self.pk, content_type=ContentType.objects.get_for_model(self))
        #     ps = Playlist.objects.exclude(type='other').filter(items__in=pis).order_by('-type', '-created',).nocache().distinct()
        # except Exception as e:
        #     pass
        #
        # return ps

    def process_master_info(self, save=False):

        # read key information from master file
        if self.master:
            file_processor = FileInfoProcessor(self.master.path)
            if file_processor.audio_stream:
                self.master_encoding = file_processor.encoding
                self.master_filesize = file_processor.filesize
                self.master_bitrate = file_processor.bitrate
                self.master_samplerate = file_processor.samplerate
                self.master_duration = file_processor.duration

            else:
                log.warning(
                    'unable to process audio file using "FileInfoProcessor"')

            if save:
                self.save()

    def save(self, *args, **kwargs):

        # Assign a default license
        """
         - not applying default license anymore
         - applying default license again: #898
        """
        if not self.license:
            try:
                license = License.objects.filter(is_default=True)[0]
                self.license = license
                log.debug('applied default license: %s' % license.name)
            except Exception as e:
                log.warning('unable to apply default license: {}'.format(e))

        self.master_changed = False
        if self.uuid is not None:
            try:
                orig = Media.objects.filter(uuid=self.uuid)[0]
                if orig.master != self.master:
                    self.master_changed = True
            except:
                pass

        if self.master_changed:
            self.process_master_info()

        # check if master changed. if yes we need to reprocess the cached files
        if self.pk is not None:

            try:
                orig = Media.objects.filter(pk=self.pk)[0]
                if orig.master != self.master:
                    log.info(
                        'Media id: %s - Master changed from "%s" to "%s"' %
                        (self.pk, orig.master, self.master))

                    # `_master_changed` can be / is used in signal listeners
                    self._master_changed = True

                    # reset processing flags
                    self.fprint_ingested = None

                    # set 'original filename'
                    if not self.original_filename and self.master.name:
                        try:
                            self.original_filename = self.master.name[0:250]
                        except Exception as e:
                            log.warning(
                                'unable to update original_filename on media: {}'
                                .format(self.pk))

            except Exception as e:
                log.warning('unable to update master: {}'.format(e))

        if self.version:
            self.version = self.version.lower()

        if self.mediatype:
            self.mediatype = self.mediatype.lower()

        # sha1 for quick duplicate checks
        if self.master and not self.master_sha1:
            self.master_sha1 = self.generate_sha1()
        else:
            self.master_sha1 = None

        unique_slugify(self, self.name)

        # pretty of ugly, clean empty relations
        for ea in MediaExtraartists.objects.filter(media__pk=self.pk):
            try:
                if not ea.artist:
                    ea.delete()
            except:
                pass

        # TODO: remove! just for testing!
        # self._master_changed = True

        super(Media, self).save(*args, **kwargs)
示例#20
0
class User(AbstractBaseUser, PermissionsMixin):
    """
    An abstract base class implementing a fully featured User model with
    admin-compliant permissions.

    Username, password and email are required. Other fields are optional.
    """

    display_name = models.CharField(max_length=30)
    username = models.CharField(
        _('username'),
        max_length=30,
        unique=True,
        db_index=True,
        help_text=_('Required. 30 characters or fewer. Letters, digits and '
                    '@/./+/-/_ only.'),
        validators=[
            validators.RegexValidator(r'^[\w.@+-]+$',
                                      _('Enter a valid username.'), 'invalid')
        ])
    slug = AutoSlugField(populate_from='username',
                         blank=True,
                         db_index=True,
                         overwrite=True,
                         editable=True,
                         unique=True)
    email = models.EmailField(_('email address'), unique=True, db_index=True)

    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_('Designates whether the user can log into this admin '
                    'site.'))
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_('Designates whether this user should be treated as '
                    'active. Unselect this instead of deleting accounts.'))
    email_verified = models.BooleanField(default=False)
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
    rank = models.ForeignKey('users.Rank',
                             related_name='users',
                             blank=True,
                             null=True)
    rank_tracker = FieldTracker(fields=['rank'])
    avatar = models.ImageField(
        upload_to=user_image_path,
        blank=True,
        null=True,
    )

    email_key_expires = models.DateTimeField(blank=True, null=True)
    key = models.CharField(max_length=32, unique=True, blank=True, null=True)

    # External UIDS
    ts_uid = models.CharField(max_length=50, blank=True,
                              null=True)  # tJL8iDNxG+1yeU5MQG61HnkC4nE=
    steam_id = models.CharField(max_length=20, blank=True,
                                null=True)  # 76561197961103742

    objects = UserManager()

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['display_name', 'email']

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')

    def get_full_name(self):
        """
        Returns the first_name plus the last_name, with a space in between.
        """
        return self.display_name

    def get_short_name(self):
        "Returns the short name for the user."
        return self.display_name

    def __unicode__(self):
        return u'%s' % self.display_name

    def generate_email_key(self):
        return uuid.uuid4().hex

    def send_verification_email(self):
        self.key = self.generate_email_key()
        self.email_key_expires = timezone.now() + timezone.timedelta(days=7)

        self.save()

        # TODO: send templated email

        context = {
            'user': self,
            'SITE_URL': settings.SITE_URL,
        }

        send_templated_mail(
            template_name='verification',
            from_email='*****@*****.**',
            recipient_list=[self.email],
            context=context,
        )

    def create_dossier(self, creator):
        defaults = {'subject': self.display_name, 'created_by': creator}
        dossier, created = Dossier.objects.get_or_create(subject_rel=self,
                                                         defaults=defaults)

        if not created:
            return

        for role_dict in self.user_roles.values():
            dossier_role = DossierRole(**role_dict)
            dossier_role.id = None
            dossier_role.subject_rel = dossier.id
            dossier_role.save()
示例#21
0
class Poodle(Model):
    slug = AutoSlugField(default=None,
                         unique=True,
                         populate_from='name_registered')
    name_call = CharField(verbose_name="Call Name", max_length=50)
    name_registered = CharField(verbose_name="Registered Name", max_length=50)
    person_owners = ManyToManyField('organizer.Person',
                                    verbose_name="Owner(s)",
                                    related_name='poodles_owners',
                                    blank=True)
    person_breeders = ManyToManyField('organizer.Person',
                                      verbose_name="Breeder(s)",
                                      related_name='poodles_breeders',
                                      blank=True)
    akc = CharField(verbose_name="AKC#", max_length=50)
    chic = CharField(verbose_name="CHIC#",
                     max_length=50,
                     blank=True,
                     default='')
    ukc = CharField(verbose_name="UKC#", max_length=50, blank=True, default='')
    addtl = CharField(verbose_name="Additional #",
                      max_length=50,
                      blank=True,
                      default='')
    sex = CharField(verbose_name="Sex", max_length=1, choices=get_tuple('sex'))
    is_altered = BooleanField(verbose_name="Altered?", default=False)
    color = CharField(verbose_name="Color",
                      max_length=2,
                      choices=get_tuple('color'),
                      default='U')
    dob = DateField(verbose_name="Date of Birth",
                    null=True,
                    blank=True,
                    default='')
    dod = DateField(verbose_name="Date of Death",
                    null=True,
                    blank=True,
                    default='')
    titles_prefix = CharField(verbose_name="Prefix Titles",
                              max_length=50,
                              blank=True,
                              default='')
    titles_suffix = CharField(verbose_name="Suffix Titles",
                              max_length=50,
                              blank=True,
                              default='')
    poodle_sire = ForeignKey('self',
                             verbose_name="Sire",
                             related_name='poodles_sire',
                             on_delete=PROTECT,
                             limit_choices_to={'sex': 'M'},
                             null=True,
                             blank=True)
    poodle_dam = ForeignKey('self',
                            verbose_name="Dam",
                            related_name='poodles_dam',
                            on_delete=PROTECT,
                            limit_choices_to={'sex': 'F'},
                            null=True,
                            blank=True)
    comments = TextField(verbose_name="Comments", blank=True, default='')
    created_at = DateTimeField(verbose_name="Created", auto_now_add=True)
    updated_at = DateTimeField(verbose_name="Updated", auto_now=True)

    class Meta:
        app_label = 'poodles'
        db_table = 'poodle'
        verbose_name_plural = 'Poodles'
        ordering = ['updated_at']

    def __str__(self):
        return '%s %s %s "%s"' % (self.titles_prefix, self.name_registered,
                                  self.titles_suffix, self.name_call)

    def url(self):
        return reverse('poodles:detail', args=[str(self.slug)])

    def titled_name(self):
        return '%s %s %s' % (self.titles_prefix, self.name_registered,
                             self.titles_suffix)

    def owners(self):
        return self.person_owners.all()

    def breeders(self):
        return self.person_breeders.all()

    def sire(self):
        return self.poodle_sire

    def dam(self):
        return self.poodle_dam

    def documents(self):
        return Document.objects.filter(poodle=self.id)

    def images(self):
        return Image.objects.filter(poodle=self.id)

    def fields(self):
        return [(field.verbose_name, field.value_to_string(self))
                for field in Poodle._meta.fields]
示例#22
0
class Project(models.Model):
    """A collection of documents which can be collaborated on"""

    objects = ProjectQuerySet.as_manager()

    user = models.ForeignKey(
        verbose_name=_("user"),
        to="users.User",
        on_delete=models.PROTECT,
        related_name="+",
        # This is set to false so we can import projects before their
        # owners
        # Once migration from old DocumentCloud is complete, this should
        # be set back to True
        db_constraint=False,
        help_text=_("The user who created this project"),
    )
    title = models.CharField(_("title"),
                             max_length=255,
                             blank=True,
                             help_text=_("The title of the project"))
    slug = AutoSlugField(
        _("slug"),
        max_length=255,
        populate_from="title",
        allow_duplicates=True,
        slugify_function=slugify,
        help_text=_("A slug for the project which may be used in a URL"),
    )
    description = models.TextField(
        _("description"),
        blank=True,
        help_text=("A description of the documents contained in this project"),
    )
    private = models.BooleanField(
        _("private"),
        default=False,
        help_text=_(
            "Private projects may only be viewed by their collaborators"),
    )

    documents = models.ManyToManyField(
        verbose_name=_("documents"),
        to="documents.Document",
        through="projects.ProjectMembership",
        related_name="projects",
        help_text=_("The documents in this project"),
    )
    collaborators = models.ManyToManyField(
        verbose_name=_("collaborators"),
        to="users.User",
        through="projects.Collaboration",
        related_name="projects",
        through_fields=("project", "user"),
    )

    created_at = AutoCreatedField(
        _("created at"),
        help_text=_("Timestamp of when the project was created"))
    updated_at = AutoLastModifiedField(
        _("updated at"),
        help_text=_("Timestamp of when the project was last updated"))

    class Meta:
        ordering = ("slug", )
        permissions = (("add_remove_project",
                        "Can add & remove documents from a project"), )

    def __str__(self):
        return self.title if self.title else "-Untitled-"

    def get_absolute_url(self):
        # Opposite order of doc url (for legacy reasons)
        return f"/projects/{self.slug}-{self.pk}/"
示例#23
0
class Proposal(TimeAuditModel):
    """ The proposals master """
    conference = models.ForeignKey(Conference)
    proposal_section = models.ForeignKey(ProposalSection,
                                         verbose_name="Proposal Section")
    proposal_type = models.ForeignKey(ProposalType,
                                      verbose_name="Proposal Type")
    author = models.ForeignKey(User, verbose_name="Primary Speaker")
    title = models.CharField(max_length=255)
    slug = AutoSlugField(max_length=255, populate_from=('title', ))
    description = models.TextField(default="")
    target_audience = models.PositiveSmallIntegerField(
        choices=PROPOSAL_TARGET_AUDIENCES,
        default=1,
        verbose_name="Target Audience")
    prerequisites = models.TextField(blank=True, default="")
    content_urls = models.TextField(blank=True, default="")
    speaker_info = models.TextField(blank=True, default="")
    speaker_links = models.TextField(blank=True, default="")
    status = models.PositiveSmallIntegerField(choices=PROPOSAL_STATUS_LIST,
                                              default=1)
    review_status = models.PositiveSmallIntegerField(
        choices=PROPOSAL_REVIEW_STATUS_LIST,
        default=1,
        verbose_name="Review Status")
    deleted = models.BooleanField(default=False, verbose_name="Is Deleted?")

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('proposal-detail',
                       args=[self.conference.slug, self.slug])

    def get_update_url(self):
        return reverse('proposal-update',
                       args=[self.conference.slug, self.slug])

    def get_review_url(self):
        return reverse('proposal-review',
                       args=[self.conference.slug, self.slug])

    def get_delete_url(self):
        return reverse('proposal-delete',
                       args=[self.conference.slug, self.slug])

    def get_up_vote_url(self):
        return reverse('proposal-vote-up',
                       args=[self.conference.slug, self.slug])

    def get_down_vote_url(self):
        return reverse('proposal-vote-down',
                       args=[self.conference.slug, self.slug])

    def get_comments_count(self):
        """ Show only the public comment count """
        return ProposalComment.objects.filter(proposal=self,
                                              deleted=False,
                                              private=False).count()

    def get_reviews_comments_count(self):
        """ Show only the public comment count """
        return ProposalComment.objects.filter(proposal=self,
                                              deleted=False,
                                              private=True).count()

    def get_votes_count(self):
        """ Show only the public comment count """
        up_vote_count = ProposalVote.objects.filter(proposal=self,
                                                    up_vote=True).count()
        down_vote_count = ProposalVote.objects.filter(proposal=self,
                                                      up_vote=False).count()
        return up_vote_count - down_vote_count

    def status_text(self):
        """ Text representation of status values """
        for value, text in PROPOSAL_STATUS_LIST:
            if self.status == value:
                return text

    class Meta:
        unique_together = ("conference", "slug")
示例#24
0
class SluggedTestModel(models.Model):
    title = models.CharField(max_length=42)
    slug = AutoSlugField(populate_from='title')
示例#25
0
class AwardType(models.Model):
    name = models.CharField(max_length=20, unique=True)
    slug = AutoSlugField(populate_from='name', unique=True)

    def __unicode__(self):
        return u'%s' % self.name
示例#26
0
class Report(models.Model):
    """
    A report retrieved from an OpenSAFELY github repo
    Currently allows for single HTML report files only
    """

    objects = ReportManager()
    category = models.ForeignKey(
        Category,
        on_delete=models.PROTECT,
        help_text="Report category; used for navigation",
        related_name="reports",
    )
    menu_name = AutoPopulatingCharField(
        max_length=60,
        populate_from="title",
        help_text="A short name to display in the side nav",
    )

    # files can be on GitHub or job-server
    # GitHub
    repo = models.CharField(
        max_length=255,
        blank=True,
        help_text="Name of the OpenSAFELY repo (case insensitive)",
    )
    branch = models.CharField(max_length=255, default="main", blank=True)
    report_html_file_path = models.CharField(
        max_length=255,
        blank=True,
        help_text="Path to the report html file within the repo",
        validators=[validate_html_filename],
    )

    # job-server
    job_server_url = models.URLField(max_length=255, default="", blank=True)

    # front matter fields
    authors = models.TextField(null=True, blank=True)
    title = models.CharField(max_length=255)
    slug = AutoSlugField(max_length=100, populate_from=["title"], unique=True)
    description = models.TextField(
        help_text=
        "Short description to display before rendered report and in meta tags",
    )
    publication_date = models.DateField(help_text="Date of first publication")
    doi = models.URLField(
        null=True,
        blank=True,
        help_text=
        "DOI for this report; note DOIs for associated publications should be "
        "entered as associated Links",
        verbose_name="DOI",
    )
    last_updated = models.DateField(
        null=True,
        blank=True,
        help_text="File last modified date; autopopulated from the file origin",
        verbose_name="Last released",
    )
    cache_token = models.UUIDField(default=uuid4)
    # Flag to remember if this report needed to use the git blob method (see github.py),
    # to avoid re-calling the contents endpoint if we know it will fail
    use_git_blob = models.BooleanField(default=False)
    is_draft = models.BooleanField(
        default=False,
        help_text=
        "Draft reports are only visible by a logged in user with relevant permissions",
    )

    contact_email = models.EmailField(default="*****@*****.**")

    class Meta:
        ordering = ("menu_name", )
        permissions = [
            ("view_draft", "Can view draft reports"),
        ]

    def __str__(self):
        return self.slug

    def refresh_cache_token(self, refresh_http_cache=True, commit=True):
        """
        Refresh cache token to invalidate http cache and clear request cache
        for hosting requests related to this repo
        """
        self.cache_token = uuid4()

        if refresh_http_cache:
            if self.uses_github:
                GithubReport(self).repo.clear_cache()
            else:
                JobServerReport(self).clear_cache()

        if commit:
            self.save()

    @property
    def meta_title(self):
        return f"{self.title} | OpenSAFELY: Reports"

    def clean(self):
        """Validate the DOI, repo, branch and report file path on save"""
        # DOIs must link to publicly accessible landing pages.  If a DOI is entered, this report
        # must be published
        if self.doi and self.is_draft:
            raise ValidationError(
                {"doi": _("DOIs cannot be assigned to draft reports")})

        # A report file must be hosted on GitHub OR job-server, so we need to
        # group the fields and validate that both the groups are valid (all
        # filled in, not all filled in) and also mutually exclusive.

        # Note self.branch has a default so we ignore it for simplicity,
        # otherwise we'll have to get the default from self._meta.fields
        github_fields = [self.repo, self.report_html_file_path]
        all_github = all(f != "" for f in github_fields)
        some_github = any(f != "" for f in github_fields)
        no_github = all(f == "" for f in github_fields)

        empty_job_server_url = self.job_server_url == ""

        # both missing
        if no_github and empty_job_server_url:
            raise ValidationError(
                "Either the GitHub or Job Server sections must be filled in.")

        # both present
        if all_github and not empty_job_server_url:
            raise ValidationError(
                "Only one of the GitHub or Job Server sections can be filled in."
            )

        # some github, no job-server
        if (some_github and not all_github) and empty_job_server_url:
            raise ValidationError(
                "All of the GitHub section must be completed.")

        # GITHUB_VALIDATION env var can optionally be set to False to skip this validation in tests
        if all_github and env.bool("GITHUB_VALIDATION", True):
            # Disable caching to fetch the repo and contents.  If this is a new report file in
            # an existing folder, we don't want to use a previously cached request
            github_report = GithubReport(self, use_cache=False)
            try:
                # noinspection PyStatementEffect
                github_report.repo
            except GithubAPIException:
                raise ValidationError({
                    "repo": _("'%(repo)s' could not be found") % {
                        "repo": self.repo
                    }
                })

            try:
                github_report.get_parent_contents()
            except GithubAPIException as error:
                # This happens if either the branch or the report file's parent path is invalid
                raise ValidationError(
                    _("Error fetching report file: %(error_message)s"),
                    params={"error_message": str(error)},
                )

            if not github_report.file_exists():
                raise ValidationError({
                    "report_html_file_path":
                    _("File could not be found (branch %(branch)s)") % {
                        "branch": self.branch
                    }
                })

        # confirm the file exists at the given location
        if not empty_job_server_url:
            job_server_report = JobServerReport(self, use_cache=False)
            if not job_server_report.file_exists():
                raise ValidationError({
                    "job_server_url":
                    mark_safe(
                        "Could not find specified file with URL: "
                        f'<a href="{self.job_server_url}">{self.job_server_url}</a>'
                    )
                })

            if not (job_server_report.is_published or self.is_draft):
                raise ValidationError({
                    "job_server_url":
                    ("Unpublished outputs cannot be used in public reports.  "
                     "Either set this report to draft or use a published output."
                     )
                })

        super().clean()

    @classmethod
    def from_db(cls, db, field_names, values):
        """Extended from_db method to store original field values on the instance"""
        instance = super().from_db(db, field_names, values)
        instance._loaded_values = dict(zip(field_names, values))
        instance._loaded_values["links"] = {
            link.pop("id"): link
            for link in instance.links.values()
        }
        return instance

    def _check_and_refresh_cache(self):
        requests_cache_fields = {"repo", "branch", "report_html_file_path"}
        # exclude fields that are autopopulated or irrelevant for http caching from the check
        exclude_fields = {
            "id",
            "slug",
            "cache_token",
            "last_updated",
            "use_git_blob",
            "is_draft",
            "links",
        }
        all_field_keys = self._loaded_values.keys()
        http_cache_fields = set(
            all_field_keys) - requests_cache_fields - exclude_fields
        if any(
                getattr(self, field) != self._loaded_values[field]
                for field in requests_cache_fields):
            logger.info(
                "Source repo field(s) updated; refreshing cache token and clearing requests cache"
            )
            self.refresh_cache_token(commit=False)
        elif any(
                getattr(self, field) != self._loaded_values[field]
                for field in http_cache_fields):
            logger.info(
                "Non-repo field(s) updated; refreshing cache token only")
            self.refresh_cache_token(refresh_http_cache=False, commit=False)

    def save(self, *args, **kwargs):
        # If updating an existing instance, check fields changed and refresh cache if required
        # For an existing instance, `from_db` will be called when the instance is retrieved from the database, and initial
        # values stored on the instance. When we call save, we will have the _loaded_values attribute.  If this save is
        # creating a new instance, the _loaded_values attribute will not be present.
        # Instances may be created, saved, and then updated and saved again without re-fetching from the db (typically in tests/shell);
        # In this case they will not have called `from_db` and will not have loaded initial values

        # call full_clean first to validate the repo fields before doing cache updates
        self.full_clean()
        initial_values_loaded_from_db = hasattr(self, "_loaded_values")
        if initial_values_loaded_from_db:
            self._check_and_refresh_cache()
        super().save(*args, **kwargs)

        # Generate the repo Link if it doesn't already exist
        source_repo_link_exists = self.links.filter(
            url__icontains=f"github.com/opensafely/{self.repo}").exists()
        if self.repo and not source_repo_link_exists:
            # get the repo url from the GithubRepo directly, to avoid calling the github api just to build the url
            repo_url = furl(
                GithubRepo(client=None, owner="opensafely",
                           name=self.repo).url)
            Link.objects.create(
                report=self,
                url=repo_url,
                label=f"Source code: opensafely/{self.repo}",
                icon="github",
            )

    def get_absolute_url(self):
        return reverse("reports:report_view", args=(self.slug, ))

    @property
    def uses_github(self):
        """
        Does this report pull its HTML file from GitHub or Jobs site?

        We validate the combination of GitHub and Jobs fields in the clean()
        method so we know that either all GitHub fields or the Jobs field will
        be populated when this is used.
        """
        return self.job_server_url == ""
示例#27
0
class Release(MigrationMixin, UUIDModelMixin, TimestampedModelMixin,
              models.Model):

    # core fields
    name = models.CharField(max_length=200, db_index=True)
    slug = AutoSlugField(populate_from='name',
                         editable=True,
                         blank=True,
                         overwrite=True)
    license = models.ForeignKey(License,
                                blank=True,
                                null=True,
                                related_name='release_license')
    release_country = models.ForeignKey(Country, blank=True, null=True)

    main_image = models.ImageField(verbose_name=_('Cover'),
                                   upload_to=upload_cover_to,
                                   storage=OverwriteStorage(),
                                   null=True,
                                   blank=True)
    catalognumber = models.CharField(max_length=50, blank=True, null=True)
    """
    releasedate stores the 'real' time, approx is for inputs
    lik 2012-12 etc.
    """
    releasedate = models.DateField(blank=True, null=True)
    releasedate_approx = ApproximateDateField(verbose_name="Releasedate",
                                              blank=True,
                                              null=True)
    pressings = models.PositiveIntegerField(default=0)
    TOTALTRACKS_CHOICES = ((x, x) for x in range(1, 301))
    totaltracks = models.IntegerField(verbose_name=_('Total Tracks'),
                                      blank=True,
                                      null=True,
                                      choices=TOTALTRACKS_CHOICES)
    asin = models.CharField(max_length=150, blank=True)
    RELEASESTATUS_CHOICES = (
        (None, _('Not set')),
        ('official', _('Official')),
        ('promo', _('Promo')),
        ('bootleg', _('Bootleg')),
        ('other', _('Other')),
    )

    releasestatus = models.CharField(max_length=60,
                                     blank=True,
                                     choices=RELEASESTATUS_CHOICES)
    excerpt = models.TextField(blank=True, null=True)
    description = extra.MarkdownTextField(blank=True, null=True)
    releasetype = models.CharField(
        verbose_name="Release type",
        max_length=24,
        blank=True,
        null=True,
        choices=alibrary_settings.RELEASETYPE_CHOICES)
    label = models.ForeignKey('alibrary.Label',
                              blank=True,
                              null=True,
                              related_name='release_label',
                              on_delete=models.SET_NULL)
    media = models.ManyToManyField('alibrary.Media',
                                   through='ReleaseMedia',
                                   blank=True,
                                   related_name="releases")

    owner = models.ForeignKey(settings.AUTH_USER_MODEL,
                              blank=True,
                              null=True,
                              related_name="releases_owner",
                              on_delete=models.SET_NULL)
    creator = models.ForeignKey(settings.AUTH_USER_MODEL,
                                blank=True,
                                null=True,
                                related_name="releases_creator",
                                on_delete=models.SET_NULL)
    last_editor = models.ForeignKey(settings.AUTH_USER_MODEL,
                                    blank=True,
                                    null=True,
                                    related_name="releases_last_editor",
                                    on_delete=models.SET_NULL)
    publisher = models.ForeignKey(settings.AUTH_USER_MODEL,
                                  blank=True,
                                  null=True,
                                  related_name="releases_publisher",
                                  on_delete=models.SET_NULL)

    barcode = models.CharField(max_length=32, blank=True, null=True)

    extra_artists = models.ManyToManyField('alibrary.Artist',
                                           through='ReleaseExtraartists',
                                           blank=True)
    album_artists = models.ManyToManyField('alibrary.Artist',
                                           through='ReleaseAlbumartists',
                                           related_name="release_albumartists",
                                           blank=True)

    relations = GenericRelation(Relation)
    d_tags = tagging.fields.TagField(max_length=1024,
                                     verbose_name="Tags",
                                     blank=True,
                                     null=True)

    objects = ReleaseManager()

    class Meta:
        app_label = 'alibrary'
        verbose_name = _('Release')
        verbose_name_plural = _('Releases')
        ordering = ('-created', )

        permissions = (
            ('view_release', 'View Release'),
            ('edit_release', 'Edit Release'),
            ('merge_release', 'Merge Releases'),
            ('admin_release', 'Edit Release (extended)'),
        )

    def __unicode__(self):
        return self.name

    @property
    def classname(self):
        return self.__class__.__name__

    @property
    def publish_date(self):
        # compatibility hack TODO: refactor all dependencies
        return datetime.utcnow()

    def is_active(self):
        now = date.today()
        try:
            if not self.releasedate:
                return True

            if self.releasedate <= now:
                return True

        except:
            pass

        return False

    @property
    def is_promotional(self):
        # TODO: refactor query to reduce db hits
        if self.releasedate:
            if self.releasedate > datetime.now().date():
                return True

        if License.objects.filter(media_license__in=self.get_media(),
                                  is_promotional=True).distinct().exists():
            return True

        return False

    @property
    def is_new(self):
        if self.is_promotional:
            return False
        if self.releasedate and self.releasedate >= (
                datetime.now() - timedelta(days=14)).date():
            return True

        return False

    def get_lookup_providers(self):

        providers = []
        for key, name in LOOKUP_PROVIDERS:
            relations = self.relations.filter(service=key)
            relation = None
            if relations.count() == 1:
                relation = relations[0]

            providers.append({'key': key, 'name': name, 'relation': relation})

        return providers

    def get_ct(self):
        return '{}.{}'.format(self._meta.app_label,
                              self.__class__.__name__).lower()

    def get_absolute_url(self):
        try:
            return reverse('alibrary-release-detail',
                           kwargs={
                               'pk': self.pk,
                               'slug': self.slug,
                           })
        except NoReverseMatch:
            translation.activate('en')
            return reverse('alibrary-release-detail',
                           kwargs={
                               'pk': self.pk,
                               'slug': self.slug,
                           })

    def get_edit_url(self):
        return reverse("alibrary-release-edit", args=(self.pk, ))

    def get_admin_url(self):
        return reverse("admin:alibrary_release_change", args=(self.pk, ))

    def get_api_url(self):
        return reverse('api_dispatch_detail',
                       kwargs={
                           'api_name': 'v1',
                           'resource_name': 'library/release',
                           'pk': self.pk
                       }) + ''

    def get_api_simple_url(self):
        return reverse('api_dispatch_detail',
                       kwargs={
                           'api_name': 'v1',
                           'resource_name': 'library/simplerelease',
                           'pk': self.pk
                       }) + ''

    def get_media(self):
        from alibrary.models import Media
        return Media.objects.filter(release=self).select_related(
            'artist', 'preflight_check')

    def get_products(self):
        return self.releaseproduct.all()

    def get_media_indicator(self):

        media = self.get_media()
        indicator = []

        if self.totaltracks:
            for i in range(self.totaltracks):
                indicator.append(0)

            for m in media:
                try:
                    indicator[m.tracknumber - 1] = 3
                except Exception as e:
                    pass

        else:
            for m in media:
                indicator.append(2)

        return indicator

    def get_license(self):

        licenses = License.objects.filter(
            media_license__in=self.get_media()).distinct()
        if not licenses.exists():
            return {'name': _(u'Not Defined')}

        if licenses.count() > 1:
            license, created = License.objects.get_or_create(name="Multiple")
            return license

        if licenses.count() == 1:
            return licenses[0]

    """
    compose artist display as string
    """

    def get_artist_display(self):

        artist_str = ''
        artists = self.get_artists()
        if len(artists) > 1:
            try:
                for artist in artists:
                    if artist['join_phrase']:
                        artist_str += ' %s ' % artist['join_phrase']
                    artist_str += artist['artist'].name
            except:
                artist_str = artists[0]['artist'].name
        else:
            try:
                artist_str = artists[0]['artist'].name
            except:
                try:
                    artist_str = artists[0].name
                except:
                    artist_str = _('Unknown Artist')

        return artist_str

    def get_artists(self):

        artists = []
        if self.album_artists.count() > 0:
            for albumartist in self.release_albumartist_release.all():
                artists.append({
                    'artist': albumartist.artist,
                    'join_phrase': albumartist.join_phrase
                })
            return artists

        medias = self.get_media()
        for media in medias:
            artists.append(media.artist)

        artists = list(set(artists))
        if len(artists) > 1:
            from alibrary.models import Artist
            a, c = Artist.objects.get_or_create(name="Various Artists")
            artists = [a]

        return artists

    def get_extra_artists(self):

        artists = []

        roles = ReleaseExtraartists.objects.filter(release=self.pk)

        for role in roles:
            try:
                role.artist.profession = role.profession.name
                artists.append(role.artist)
            except:
                pass

        return artists

    def get_downloads(self):
        return None

    def get_download_url(self, format, version):

        return '%sdownload/%s/%s/' % (self.get_absolute_url(), format, version)

    def get_cache_file_path(self, format, version):

        tmp_directory = TEMP_DIR
        file_name = '%s_%s_%s.%s' % (format, version, str(self.uuid), 'zip')
        tmp_path = '%s/%s' % (tmp_directory, file_name)

        return tmp_path

    def clear_cache_file(self):

        tmp_directory = TEMP_DIR
        pattern = '*%s.zip' % (str(self.uuid))
        versions = glob.glob('%s/%s' % (tmp_directory, pattern))

        try:
            for version in versions:
                os.remove(version)

        except Exception as e:
            pass

    def get_cache_file(self, format, version):

        cache_file_path = self.get_cache_file_path(format, version)

        if os.path.isfile(cache_file_path):
            logger.info('serving from cache: %s' % (cache_file_path))
            return cache_file_path

        else:
            return self.build_cache_file(format, version)

    def build_cache_file(self, format, version):

        cache_file_path = self.get_cache_file_path(format, version)

        logger.info('building cache for: %s' % (cache_file_path))

        try:
            os.remove(cache_file_path)

        except Exception as e:
            pass

        archive_file = ZipFile(cache_file_path, "w")
        """
        adding corresponding media files
        """
        for media in self.get_media():
            media_cache_file = media.inject_metadata(format, version)

            # filename for the file archive
            file_name = '%02d - %s - %s' % (media.tracknumber,
                                            media.artist.name, media.name)
            file_name = '%s.%s' % (file_name.encode('ascii', 'ignore'), format)

            archive_file.write(media_cache_file.path, file_name)

        return cache_file_path

    def get_extraimages(self):
        return None

    # OBSOLETE
    def complete_by_mb_id(self, mb_id):

        obj = self

        log = logging.getLogger('alibrary.release.complete_by_mb_id')
        log.info('complete release, r: %s | mb_id: %s' % (obj.name, mb_id))

        inc = ('artists', 'url-rels', 'aliases', 'tags', 'recording-rels',
               'work-rels', 'work-level-rels', 'artist-credits')
        url = 'http://%s/ws/2/release/%s/?fmt=json&inc=%s' % (
            MUSICBRAINZ_HOST, mb_id, "+".join(inc))

        r = requests.get(url)
        result = r.json()

        return obj

    def save(self, *args, **kwargs):

        self.clear_cache_file()
        unique_slugify(self, self.name)

        # convert approx date to real one
        ad = self.releasedate_approx
        try:
            ad_y = ad.year
            ad_m = ad.month
            ad_d = ad.day
            if ad_m == 0:
                ad_m = 1
            if ad_d == 0:
                ad_d = 1

            rd = datetime.strptime('%s/%s/%s' % (ad_y, ad_m, ad_d), '%Y/%m/%d')
            self.releasedate = rd
        except:
            self.releasedate = None

        if hasattr(self, '_last_editor') and getattr(self, '_last_editor'):
            last_editor = getattr(self, '_last_editor')
            self.last_editor = last_editor
        else:
            last_editor = None

        logger.debug('saved release id: {} - user: {} - caller: {}'.format(
            self.pk, last_editor,
            inspect.stack()[1][3]))

        super(Release, self).save(*args, **kwargs)
示例#28
0
class EventApplication(models.Model):
    previous_event = models.ForeignKey(Event,
                                       blank=True,
                                       null=True,
                                       on_delete=models.deletion.SET_NULL)
    # workshop fields
    date = ApproximateDateField(validators=[validate_approximatedate])
    city = models.CharField(max_length=200)
    country = models.CharField(max_length=200, choices=countries)
    latlng = models.CharField(
        max_length=30,
        blank=True,
        null=True,
    )
    website_slug = AutoSlugField(populate_from='city', editable=True)
    main_organizer_email = models.EmailField()
    main_organizer_first_name = models.CharField(max_length=30)
    main_organizer_last_name = models.CharField(max_length=30,
                                                blank=True,
                                                default="")
    created_at = models.DateTimeField(auto_now_add=True)

    # application fields
    about_you = models.TextField(_("About organizer"))
    why = models.TextField(_("Motivations to organize"))
    involvement = models.CharField(_("Involvement in Django Girls"),
                                   max_length=100)
    experience = models.TextField(_("Experience with organizing other events"))
    venue = models.TextField(_("Information about your potential venue"),
                             blank=True)
    sponsorship = models.TextField(
        _("Information about your potential sponsorship"))
    coaches = models.TextField(_("Information about your potential coaches"))
    remote = models.BooleanField(default=False)
    tools = models.TextField(
        _("Information about how you will host your remote workshop"),
        blank=True)
    safety = models.TextField(_(
        "Information about how you will ensure participants' and coaches' safety during the Covid-19 pandemic"
    ),
                              blank=True)
    diversity = models.TextField(
        _("Information about how you intend to ensure your workshop is inclusive "
          "and promotes diversity"))
    additional = models.TextField(
        _("Any additional information you think may help your application"),
        blank=True)

    # status reflecting state of the event in a triaging process.
    status = models.CharField(
        choices=APPLICATION_STATUS,
        default=NEW,
        max_length=10,
    )
    status_changed_at = models.DateTimeField(null=True, blank=True)

    comment = models.TextField(null=True, blank=True)

    object = EventApplicationManager()
    objects = EventApplicationQuerySet.as_manager()

    class Meta:
        permissions = (("can_accept_organize_application",
                        _("Can accept Organize Applications")), )

    def __str__(self):
        return f"{self.city}, {self.get_country_display()} ({self.get_status_display()})"

    def save(self, *args, **kwargs):
        if not self.latlng:
            self.latlng = get_coordinates_for_city(self.city,
                                                   self.get_country_display())

        super().save(*args, **kwargs)

    def create_event(self):
        """ Creates event based on the data from the EventApplication.
        """
        name = f'Django Girls {self.city}'
        email = f'{self.website_slug}@djangogirls.org'

        event = Event.objects.create(
            date=self.date,
            city=self.city,
            country=self.get_country_display(),
            latlng=self.latlng,
            page_url=self.website_slug,
            name=name,
            page_title=name,
            email=email,
        )

        # populate content & menu from the default event
        event.add_default_content()
        event.add_default_menu()

        # Add a random cover picture to the event
        event.set_random_cover()
        event.save()

        return event

    def has_past_team_members(self, event):
        """ For repeated events, check whether there are any common
        team members who applied to organize again
        """
        previous_event = Event.objects.filter(
            city=self.city, country=self.get_country_display()).exclude(
                pk=event.pk).order_by('-id').first()

        if previous_event:
            organizers = previous_event.team.all().values_list('email',
                                                               flat=True)
            applicants = self.get_organizers_emails()
            return len(set(organizers).intersection(applicants)) > 0
        return False

    @transaction.atomic
    def deploy(self):
        """ Deploy Event based on the current EventApplication
            - change status to DEPLOYED
            - creates or copies event
            - add/remove organizers
            - send email about deployment
        """
        if self.status == DEPLOYED:  # we don't want to deploy twice
            return

        self.change_status_to(DEPLOYED)

        event = None
        previous_event = (Event.objects.filter(
            city=self.city,
            country=self.get_country_display()).order_by("-date").first())

        if previous_event:
            event = copy_event(previous_event, self.date)
        else:
            event = self.create_event()

        # add main organizer of the Event
        main_organizer = event.add_organizer(
            self.main_organizer_email,
            self.main_organizer_first_name,
            self.main_organizer_last_name,
        )
        event.main_organizer = main_organizer
        event.save()

        # add all co-organizers
        for organizer in self.coorganizers.all():
            event.add_organizer(organizer.email, organizer.first_name,
                                organizer.last_name)
        return event

    def send_deployed_email(self, event):
        # sort out Gmail accounts
        dummy_email, email_password = gmail_accounts.get_or_create_gmail(
            event_application=self, event=event)

        # TODO: remove organizers, who are no longer in org team if cloned
        send_application_deployed_email(event_application=self,
                                        event=event,
                                        email_password=email_password)

    def clean(self):
        if self.status == ON_HOLD and not self.comment:
            raise ValidationError({'comment': _('This field is required.')})

    def get_organizers_emails(self):
        """
        Returns a list of emails to all organizers in that application
        """
        emails = [coorganizer.email for coorganizer in self.coorganizers.all()]
        emails.append(self.main_organizer_email)
        return emails

    def get_main_organizer_email(self):
        return self.main_organizer_email

    def get_main_organizer_name(self):
        return f'{self.main_organizer_first_name} {self.main_organizer_last_name}'

    def change_status_to(self, status):
        """ Changes status to the status provided
            - sets proper status_changed_at datetime
        """
        self.status = status
        self.status_changed_at = timezone.now()
        self.save(update_fields=['status', 'status_changed_at'])

    @transaction.atomic
    def reject(self):
        """
        Rejecting event in triaging. Performs following actions:
        - changes status to REJECTED
        - sends a rejection email
        """
        if not self.status == REJECTED:
            self.change_status_to(REJECTED)
            send_application_rejection_email(event_application=self)
示例#29
0
class Request(models.Model):
    author = models.ForeignKey(User)
    title = models.CharField(max_length=255, blank=True)
    status = models.CharField(max_length=1, choices=request_statuses,)
    government = models.ForeignKey(Government, null=True, blank=True)
    agency = models.ForeignKey(Agency, blank=True, null=True)
    documents = models.ManyToManyField(Document, blank=True, null=True, related_name='related_docs')
    contacts = models.ManyToManyField(Contact, blank=True, null=True, related_name='related_contacts')
    text = models.TextField(u'Request text', blank=True)
    free_edit_body = models.TextField(u'Request text', blank=True)
    attachments = models.ManyToManyField(Attachment, blank=True, null=True)
    printed = models.ForeignKey(Attachment, blank=True, null=True, related_name='printed_request')
    private = models.BooleanField('Mark this request as private', default=True)
    supporters = models.ManyToManyField(User, blank=True, null=True, related_name='supporter')
    slug = AutoSlugField(populate_from=('title', ), overwrite=False, blank=True)
    date_added = models.DateTimeField(auto_now_add=True)
    date_updated = models.DateTimeField(auto_now=True)
    date_fulfilled = models.DateTimeField(blank=True, null=True)
    due_date = models.DateTimeField(blank = True, null = True)
    scheduled_send_date = models.DateTimeField(blank=True, null=True)
    request_start_date = models.DateTimeField(blank=True, null=True)
    request_end_date = models.DateTimeField(blank=True, null=True)
    keep_private = models.BooleanField('Never make this request public by default', default=False)
    #Different from response formats: things like "meeting minutes", e.g.
    record_types = models.ManyToManyField(RecordType, blank=True, null=True)
    acceptable_responses = models.ManyToManyField(ResponseFormat, blank=True, null=True)
    fee_waiver = models.BooleanField(default=True)
    phone_contact = models.BooleanField(default=True)
    prefer_electornic = models.BooleanField(default=True)
    max_cost = models.IntegerField(default=0, blank=True)
    thread_lookup = models.CharField(max_length=255, blank=True)


    last_contact_date = models.DateTimeField(blank=True, null=True)
    first_response_time = models.IntegerField(default=0, blank = True)
    lifetime = models.IntegerField(default = 0, blank = True)
    days_outstanding = models.IntegerField(default = 0, blank = True)
    response_overdue = models.BooleanField(default = False)
    official_stats = models.BooleanField(default = False)


    # Managers
    objects = MyRequestManager()

    private_objects = PrivateRequestManager()
    public_objects = PublicRequestManager()

    tags = TaggableManager(blank=True)

    @property
    def get_contacts_with_email(self):
        retval = []
        for contact in self.contacts.all():
            if contact.get_first_active_email is None:
                retval.append(contact)
        return retval

    def set_status(self, status_str):
        old_status = self.status
        for rs in request_statuses:
            if status_str == rs[1] or status_str == rs[0]:
                self.status = rs[0]
                new_status = self.status

                if old_status == 'S' and (new_status in ['R','P','F','D']) and self.scheduled_send_date is not None:
                    # Our first response from the agency
                    now = datetime.now(tz=pytz.utc)
                    holidays = self.government.get_holiday_dates
                    self.first_response_time = workdays.networkdays(self.scheduled_send_date, now, holidays)
                    if self.first_response_time == 0:
                        self.first_response_time = 1


                if old_status in ['S', 'R', 'P'] and new_status in ['F', 'D'] and self.scheduled_send_date is not None:
                    now = datetime.now(tz=pytz.utc)
                    holidays = self.government.get_holiday_dates
                    self.lifetime = workdays.networkdays(self.scheduled_send_date, now, holidays)
                    if self.lifetime == 0:
                        self.lifetime = 1
                self.save()
        return self.get_status

    @property
    def get_status(self):
        for rs in request_statuses:
            if self.status == rs[0]:
                return rs[1]
        return self.status

    @property
    def get_privacy_string(self):
        shared_with = get_groups_with_perms(self)
        retval = "Nothing"
        if self.private == True and len(shared_with) == 1:
            retval = "This request is private"
        elif self.private == True and len(shared_with) > 1:#TODO DOUBLE CHECK ME
            retval = "This request is private but shared with others"
        else:
            retval = "This request is public"
        return retval

    @property
    def get_title_string(self):
        if self.title == '':
            return 'Unspecified'
        return self.title

    @property
    def get_status_color(self):
        try:
            mapp = {
                'X': '#d73027',
                'I': '#f46d43',
                'U': '#fdae61',
                'S': '#fee090',
                'R': '#e0f3f8',
                'P': '#abd9e9',
                'F': '#74add1',
                'D': '#4575b4'
            }
            return mapp[self.status]
        except:
            return ""

    @property
    def get_due_date_string(self):
        if self.due_date:
            return self.due_date.strftime("%b %d, %Y")
        return 'NA'

    @property
    def get_date_added_string(self):
        if self.date_added:
            try:
                return self.date_added.strftime("%b %d, %Y")
            except Exception as e:
                logger.error(e)
        return 'NA'

    @property
    def get_date_updated_string(self):
        if self.date_added:
            try:
                return self.date_updated.strftime("%b %d, %Y")
            except Exception as e:
                logger.error(e)
        return 'NA'

    @property
    def get_agency_string(self):
        if self.agency:
            url = reverse('agency_detail', args=(self.agency.slug,))
            return "<a href='%s'>%s</a>" % (url, self.agency.name)
        return "NA"

    @property
    def get_government_string(self):
        if self.government:
            retval = ""
            for statute in self.government.statutes.all():
                url = reverse("statute_detail", args=(statute.slug,))
                retval += "<a href='%s'>%s</a>" % (url, statute.short_title)
            return retval
        return "NA"

    @property
    def get_tags_string(self):
        retval = ""
        tags = excludeHiddenTags(self.tags.all())
        if len(tags) <= 0:
            return "None"
        for idx, tag in enumerate(tags):
            retval += "<a href='?tags=%s&filtering=1'>%s</a>" % (tag.id, tag.name)
            if idx != len(tags) - 1:
                retval += ', '
        return retval

    @property
    def get_detail_url(self):
        return reverse("request_detail", args=(self.id,))

    @property
    def can_send(self):
        if self.sent:
            return False
        if self.contacts.all().count() < 1:
            return False
        if self.agency is None:
            return False
        if self.government is None:
            return False
        if self.free_edit_body == '':
            return False
        return True

    @property
    def sent(self):
        #this is the for sure way but scheduled_send_date is set when the object is mailed and we currently have no scheduler
        from apps.mail.models import MailBox
        mb = MailBox.objects.get_or_create(usr=self.author)[0]
        threads = mb.get_threads(self.id)
        #TODO update this so it checks the sent date, because now people can send emails to an unsent request
        if len(threads) > 0:
            return True
        return False

    @property
    def get_due_date(self):
        #get statute with least number of days till maturity
        #TODO: scheduled send date should probably be required and checked against the first sent message
        if self.due_date:
            return self.due_date
        if self.government is None:
            return None
        statutes = self.government.get_statutes
        holidays = self.government.get_holiday_dates
        if statutes is not None and self.scheduled_send_date is not None:
            if len(statutes) > 0:
                soonest_statute = statutes[0]
            else:
                return None
            days_till_due = soonest_statute.get_days_till_due
            if days_till_due:
                sent = self.scheduled_send_date
                due_when = workdays.workday(sent, days_till_due, holidays)
                self.due_date = due_when
            else:
                self.due_date = None
            self.save()
            return self.due_date
        return None

    @property
    def get_days_till_due(self):
        due_date = self.get_due_date
        if due_date is not None:
            dt = due_date - self.scheduled_send_date
            return dt.days
        return None

    class Meta:
        permissions = (
            ('view_this_request', 'View request'),
            ('edit_this_request', 'Edit request'),
            ('delete_this_request', 'Delete request'),
        )

    @staticmethod
    def get_permission_name(key):
    #provide map for standard permissions
        permissions_map = {
            'view': 'view_this_request',
            'edit': 'edit_this_request',
            'delete': 'delete_this_request'
        }
        try:
            return permissions_map[key]
        except KeyError:
            return ''

    @staticmethod
    def get_permissions_path(key):
        return 'requests.%s' % Request.get_permission_name(key)

    def __unicode__(self):
        if self.agency:
            return '%s: %s %s %s' % (self.agency, self.date_added, self.title, self.id)
        elif self.government:
            return '%s (No agency yet): %s %s %s' % (self.government, self.date_added, self.title, self.id)
        else:
            return '%s %s' % (str(self.date_added), self.id)

    @property
    def letter_header(self):
        address = ''
        for c in self.contacts.all():
            address += '<div class="well"><address><div class="contact-name">%s %s</div>' % (c.first_name, c.last_name,)
            title = c.get_recent_title()
            if title:
                 address += '<div class="contact-address">%s</div>' % (title.get_content,)
            snail_mail = c.get_recent_address()
            if snail_mail:
                address += '<div class="contact-address">%s</div>' % (snail_mail.get_content,)
            emails = c.get_active_emails()
            if emails:
                emails = [email.get_email for email in emails]
                address += '<div class="contact-email">%s</div></address>' % (','.join(emails))
            address += '<p>Dear %s:</p></div>' % (c.first_name,)
        return address

    @property
    def letter_body(self):
        try:
            body = ''
            #law citation handling
            law_texts = []
            statutes = self.government.statutes.all() if self.government is not None else []
            for l in statutes:
                law_texts.append('%s\'s %s (%s)' % (self.government.name, l.short_title, l.designator,))
            #short_title, designator
            body += '<p>Pursuant to %s, I hereby request the following records:</p>' % (' and '.join(law_texts))
            #items requested
            body += '<p>%s</p>' % (self.text,)
            #formats
            if self.acceptable_responses.all().count() == 1:
                body += '<p>I would like to request that my request be fulfilled in the form of a %s.</p>'\
                 % (self.acceptable_responses.all()[0],)
            elif self.acceptable_responses.all().count() > 0:
                formats = '<p>I would like my request be fulfilled in one of these electronic formats:</p><ul>'
                for f in self.acceptable_responses.all():
                    formats += '<li>%s</li>' % (f,)
                formats += '</ul>'
                body += formats
            #fee warning
            cost_graf = ''
            if len(statutes) > 0:
                cost_graf = 'Under the %s the government is allowed to charge only the cost of\
                 copying materials.' % (statutes[0].short_title,)
            #TODO: fee waiver
            if self.fee_waiver:
                cost_graf += ' I am requesting that you waive all applicable fees associated with this request as I believe this request is in the public interest and is not for commercial use. Release of this information is in the public interest because it will contribute significantly to public understanding of government operations and activities. If you deny this request for a fee waiver, please advise me in advance of the estimated charges'
                if self.max_cost == 0:
                    cost_graf += ' associated with fulfilling this request.'
                else:
                    cost_graf += ' if they are to exceed $%s.' % (self.max_cost,)
            else:
                #no fee waiver
                if self.max_cost == 0:
                    cost_graf += ' Please advise me in advance of the estimated charges associated with fulfilling this request.'
                else:
                    cost_graf += ' Please advise me in advance of the estimated charges if they are to exceed $%s.' % (self.max_cost,)

            cost_graf += ' Please send me a detailed and itemized explanation of those charges.'
            body += '<p>%s</p>' % (cost_graf,)
            misc_graf = ''
            if self.prefer_electornic:
                misc_graf += 'In the interest of expediency, and to minimize the research and/or duplication burden on your staff, please send records electronically if possible.  If this is not possible, please notify me before sending to the address listed below.'
            if self.phone_contact:
                from apps.users.models import UserProfile
                phone = UserProfile.objects.get(user=self.author).phone
                misc_graf += ' Since time is a factor, please communicate with me by telephone or this email address. I can be reached at %s' % phone

            body += '<p>%s</p>' % (misc_graf,)

            #contact graf
            body += '<p>Please contact me if you have any questions about my request.</p>'
        except Exception as e:
            logger.exception(e)
        return body

    @property
    def letter_signature(self):
        from apps.users.models import UserProfile
        authorprofile = UserProfile.objects.get(user=self.author)
        #TODO set users phone number
        retval = '<p>%s %s</p>' % (self.author.first_name, self.author.last_name,)
        retval += '<p>%s<br/>%s<br/>%s<br/>%s, %s %s</p>'\
             % (self.author.email, authorprofile.phone, authorprofile.mailing_address,  authorprofile.mailing_city, authorprofile.mailing_state, authorprofile.mailing_zip,)
        return retval

    @property
    def letter_html(self):
        letter = '%s%s<p>Sincerely,</p>%s' % (self.letter_header, self.letter_body, self.letter_signature,)
        return letter

    def create_pdf_body(self):
        from apps.mail.models import Attachment
        from xhtml2pdf import pisa
        import os
        try:
            #ghetto tmp file for pdf creation, will this work on HEROKU? I think so but could be slow...
            if not os.path.exists('tmp'):
                os.makedirs('tmp')
            #TODO: need default attachment
            attachment = None
            html = self.free_edit_body
            fname = 'tmp/request_%s_tmp.pdf' % self.id
            with open(fname, 'wb') as f:
                doc = pisa.pisaDocument(html, f)
            if not doc.err:
                with open(fname, 'rb') as f:
                    to_file = ContentFile(f.read())
                    attachment = Attachment()
                    attachment.user = self.author
                    attachment.file.save('request_%s.pdf' % self.id, to_file)
                    attachment.save()
                os.remove(fname)
            else:
                logger.error("error writing to PDF: %s" % doc.err)
            self.printed = attachment
            self.save()
            return attachment.url
        except Exception as e:
            logger.exception(e)
            return None 

    def send(self, attachments=[]):
        if self.sent:
            return True
        try:
            #avoid circular imports
            from apps.mail.models import MailBox, MailMessage, MSG_DIRECTIONS
            mailbox, created = MailBox.objects.get_or_create(usr=self.author)
            #attachment = self.create_pdf_body()
            tos = []
            for contact in self.contacts.all():
                tos += [email for email in contact.get_active_emails()]
            bcc_email, created = EmailAddress.objects.get_or_create(content=self.author.email)
            subject = self.title if self.title is not None and self.title != '' else 'Open Records Request for %s %s' % (contact.first_name, contact.last_name,)
            message = MailMessage(email_from=mailbox.get_registered_email(),
                subject=subject,\
                body=self.free_edit_body.encode('utf8'), reply_to=mailbox.get_provisioned_email(),\
                direction=MSG_DIRECTIONS[0][0], request=self)
            message.save()

            message.to = tos
            message.bcc = [bcc_email]
            #message.attachments = [attachment] if attachment is not None else []
            message.attachments = []
            if attachments:
                for attach in attachments:
                    message.attachments.add(attach)
            for attach in self.attachments.all():
                message.attachments.add(attach)
            sent = message.send(mailbox.get_provisioned_email())
            mailbox.add_message(message)
            #update status
            self.status = 'S'
            self.scheduled_send_date = timezone.now()
            self.due_date = self.get_due_date
            self.save()
            if sent is None:
                return False
            return True
        except Exception as e:
            logger.exception(e)
            return False 

    @property
    def privacy_status(self):
        if self.private:
            return 'Private'
        else:
            return 'Public'

    @property
    def time_outstanding(self):
        date_filed = self.date_added
        if self.date_fulfilled:
            final_date = self.date_fulfilled
        else:
            final_date = timezone.now()
        date_diff = final_date - date_filed
        return date_diff.days

    def original_deadline(self):
        try: # AHHHH HACK CITY!!!
            e = self.event_set.filter(type=2).order_by('date')[0]
        except IndexError:
            return
        return e.date

    @property
    def latest_deadline(self):
        try: # AHHHH HACK CITY!!!
            e = self.event_set.filter(type=2).order_by('date')[0]
        except IndexError:
            return
        return e.date

    @property
    def is_late_naive(self):
        """
        Naive representation of whether a response is late. Will
        want to redo this with more 
        """
        is_late = False
        if self.latest_deadline: # AHHHH HACK CITY!!!
            if datetime.date.today() > self.latest_deadline:
                is_late = True
        return is_late


    @models.permalink
    def get_absolute_url(self):
        return ('request_detail', (), {'pk': self.pk})

    def save(self, *args, **kw):
        #TODO: abort save if sent
        if self.pk is not None:
            orig = Request.objects.get(pk=self.pk)
            if orig.private != self.private:
                logger.info("request %s privacy changed to=%s from=%s" % (self.slug, self.private, orig.private))
                group, g_created = Group.objects.get_or_create(name='public')
                if self.private == True:
                    remove_perm(Request.get_permission_name('view'), group, self)
                    logger.info('request %s permissions changed: removed from public' % (self.slug))
                else:
                    assign(Request.get_permission_name('view'), group, self)
                    logger.info('request %s permissions changed: added to public' % (self.slug))
            if self.contacts is not None and self.contacts.count() > 0 and self.contacts.all()[0].get_related_agencies().count() > 0:
                self.agency = self.contacts.all()[0].get_related_agencies()[0]
                self.government = self.agency.government
            else:
                self.agency = None
                self.government = None
        else:
            self.status = 'I'
            #code = "LOOKUP:" + User.objects.make_random_password(length=64)
            code = '2016' + '_payroll_' + self.agency.name.replace(' ','') 

            #while Request.objects.filter(thread_lookup=code):
            #    code = User.objects.make_random_password(length=64)
            self.thread_lookup = code

        super(Request, self).save(*args, **kw)

    @staticmethod
    def get_user_in_threshold(user, days=7):
        now = datetime.now(tz=pytz.utc)
        threshold = now - timedelta(days=days)
        return Request.objects.filter(author=user, date_added__gte=threshold, status__in=['S','R','P','F','D'])

    @staticmethod
    def get_all_overdue(days=1):
        now = datetime.now(tz=pytz.utc).strftime("%Y-%m-%d") + " 23:59:59"
        query = (
            "select requests_request.id from requests_request"
            " where requests_request.due_date <= '%s' and requests_request.due_date"
            " is not null and requests_request.status = 'S' and"
            " requests_request.id not in (select request_id from requests_notification where type = %s);" % (now, Notification.get_type_id('Late request'))
            )
        results = Request.objects.raw(query, translations={'requsts_request.id': 'id'})
        return set(results)

    @staticmethod
    def get_all_sunsetting(sunset_days):
        now = (datetime.now(tz=pytz.utc) + timedelta(-1 * (sunset_days-1))).strftime("%Y-%m-%d")
        query = (
            "select requests_request.id from requests_request"
            " where requests_request.scheduled_send_date <= '%s' and requests_request.scheduled_send_date"
            " is not null and requests_request.private = 1 and requests_request.keep_private = 0 and"
            " requests_request.status != 'X' and requests_request.status != 'I' and requests_request.status != 'U' and"
            " requests_request.id not in (select request_id from requests_notification where type = %s);" % (now, Notification.get_type_id('Sunset clause notification'))
            )
        results = Request.objects.raw(query, translations={'requsts_request.id': 'id'})
        return set(results)

    @staticmethod
    def get_sunsetted(sunset_days):
        now = (datetime.now(tz=pytz.utc) + timedelta(-1 * (sunset_days-1))).strftime("%Y-%m-%d") + " 23:59:59"
        #only want to make requests public for users who have received a notification of sunset
        #BUT the raw query defers model loading which breaks django_guardian
        #query = (
        #    "select requests_request.id from requests_request"
        #    " where requests_request.scheduled_send_date <= '%s' and requests_request.scheduled_send_date"
        #    " is not null and requests_request.status = 'S' and requests_request.private = 1 and requests_request.keep_private = 0 and"
        #    " requests_request.id in (select request_id from requests_notification where type = %s);" % (now, Notification.get_type_id('Sunset clause notification'))
        #    )
        #results = Request.objects.raw(query, translations={'requsts_request.id': 'id'})
        #return set(results)
        return Request.objects.filter(private=True, scheduled_send_date__lte=now, keep_private=False)
示例#30
0
class SluggedTestModel(models.Model):
    title = models.CharField(max_length=42)
    slug = AutoSlugField(populate_from='title')

    class Meta:
        app_label = 'django_extensions'