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'
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
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)
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
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
class Suggester(CrawledItem): """Submittter of a suggestion.""" slug = AutoSlugField(populate_from='title', max_length=120) title = models.TextField()
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)
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
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', ))
class Tag(models.Model): name = AutoSlugField(unique=True, max_length=30, populate_from=['name']) def __str__(self): return self.name
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
class FunctionSluggedTestModel(models.Model): title = models.CharField(max_length=42) slug = AutoSlugField(populate_from=get_readable_title) class Meta: app_label = 'django_extensions'
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()
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']
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')
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
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)
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()
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]
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}/"
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")
class SluggedTestModel(models.Model): title = models.CharField(max_length=42) slug = AutoSlugField(populate_from='title')
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
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 == ""
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)
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)
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)
class SluggedTestModel(models.Model): title = models.CharField(max_length=42) slug = AutoSlugField(populate_from='title') class Meta: app_label = 'django_extensions'