class Command(NoArgsCommand): default_interval = 60 help = dedent(u"""\ Dummy cron server for local development. Repeatedly calls ``runcrons`` management command executing all sheduled cron jobs.""") option_list = NoArgsCommand.option_list + ( make_option(u'--interval', action=u'store', type=u'int', dest=u'interval', default=default_interval, help=squeeze(u""" Interval in seconds how often to check if there are jobs to run. Defaults to {} secons. """).format(default_interval)), make_option(u'--clearlogs', action=u'store_true', dest=u'clearlogs', default=False, help=squeeze(u""" Clear cron logs before running the server like no cron jobs have ever been run yet. """)), ) def handle_noargs(self, **options): interval = options[u'interval'] clearlogs = options[u'clearlogs'] try: if clearlogs: CronJobLog.objects.all().delete() while True: # If we are timewarping, we may encounter cron logs from future. We must remove # them, otherwise django_cron won't run any jobs with logs from furure. CronJobLog.objects.filter(end_time__gt=utc_now()).delete() call_command(u'runcrons') time.sleep(interval) except KeyboardInterrupt: pass
class InvitationAdminBulkAddForm(forms.Form): emails = forms.CharField( validators=[ValidatorChain( validate_comma_separated_emails, validate_unused_emails, )], widget=admin.widgets.AdminTextareaWidget(), help_text=squeeze(u""" Comma separated list of email address to invite. """), ) validity = forms.IntegerField( min_value=0, initial=app_settings.DEFAULT_VALIDITY, help_text=squeeze(u""" Number of days the invitation will be valid. """), ) invitor = Invitation._meta.get_field(u'invitor').formfield( widget=admin.widgets.ForeignKeyRawIdWidget( Invitation._meta.get_field(u'invitor').rel, admin.site), ) send_email = forms.BooleanField( initial=True, required=False, help_text=squeeze(u""" Check to send the invitation e-mail to the given address. Leave the checkbox empty if you just want to create a fake invitation without sending any real e-mail. For instance, if you want to send the invitation e-mail manually. """), ) class _meta: model = Invitation labels = None help_texts = None def __init__(self, *args, **kwargs): self.instance = Invitation() user = kwargs.pop(u'user') super(InvitationAdminBulkAddForm, self).__init__(*args, **kwargs) self.initial[u'invitor'] = user def save(self): assert self.is_valid() invitations = [] for name, email in getaddresses([self.cleaned_data[u'emails']]): invitation = Invitation.create( email=email, invitor=self.cleaned_data[u'invitor'], validity=self.cleaned_data[u'validity'], send_email=self.cleaned_data[u'send_email'], ) invitation.save() invitations.append(invitation) return invitations
class District(FormatMixin, models.Model): # "Okres" # Primary key id = models.CharField(max_length=32, primary_key=True, help_text=squeeze(u""" District primary key. Example: "SK031B" (REGPJ.LSUJ1) """)) # Should NOT be empty name = models.CharField(max_length=255, unique=True, help_text=squeeze(u""" Unique human readable district name. (REGPJ.NAZOKS, REGPJ.NAZLSUJ1) """)) # Should NOT be empty; Read-only; Automaticly computed in save() slug = models.SlugField(max_length=255, unique=True, help_text=squeeze(u""" Unique slug to identify the district used in urls. Automaticly computed from the district name. May not be changed manually. """)) # May NOT be NULL region = models.ForeignKey(Region, help_text=u'Region the district belongs to.') # Backward relations: # # -- municipality_set: by Municipality.district # May be empty # # -- neighbourhood_set: by Neighbourhood.district # May be empty # Backward relations added to other models: # # -- Region.district_set # May be empty # Indexes: # -- id: primary_key # -- name: unique # -- slug: unique # -- region: ForeignKey objects = DistrictQuerySet.as_manager() @decorate(prevent_bulk_create=True) def save(self, *args, **kwargs): update_fields = kwargs.get(u'update_fields', None) # Generate and save slug if saving name if update_fields is None or u'name' in update_fields: self.slug = slugify(self.name) if update_fields is not None: update_fields.append(u'slug') super(District, self).save(*args, **kwargs) def __unicode__(self): return u'[{}] {}'.format(self.pk, self.name)
class ObligeeAlias(FormatMixin, models.Model): # May NOT be NULL obligee = models.ForeignKey(Obligee, help_text=u'Obligee of which this is alias.') # Should NOT be empty name = models.CharField(max_length=255, unique=True, help_text=squeeze(u""" Unique human readable obligee alias if the obligee has multiple common names. """)) # Should NOT be empty; Read-only; Automaticly computed in save() slug = models.SlugField(max_length=255, unique=True, help_text=squeeze(u""" Unique slug to identify the obligee alias used in urls. Automaticly computed from the obligee name. May not be changed manually. """)) # May be empty description = models.TextField(blank=True, help_text=u'Obligee alias description.') # May be empty notes = models.TextField( blank=True, help_text=u'Internal freetext notes. Not shown to the user.') # Backward relations added to other models: # # -- Obligee.obligeealias_set # May be empty # Indexes: # -- obligee: ForeignKey # -- name: unique # -- slug: unique objects = ObligeeAliasQuerySet.as_manager() class Meta: verbose_name_plural = u'obligee aliases' @decorate(prevent_bulk_create=True) def save(self, *args, **kwargs): update_fields = kwargs.get(u'update_fields', None) # Generate and save slug if saving name if update_fields is None or u'name' in update_fields: self.slug = slugify(self.name) if update_fields is not None: update_fields.append(u'slug') super(ObligeeAlias, self).save(*args, **kwargs) def __unicode__(self): return u'[{}] {}'.format(self.pk, self.name)
class ObligeeGroup(FormatMixin, models.Model): # May NOT be empty key = models.CharField(max_length=255, unique=True, help_text=squeeze(u""" Unique key to identify the group. The key is a path of slash separated words each of which represents a parent group. Every word in the path should be a nonempty string and should only contain alphanumeric characters, underscores and hyphens. """)) # Should NOT be empty name = models.CharField(max_length=255, unique=True, help_text=squeeze(u""" Unique human readable group name. """)) # Should NOT be empty; Read-only; Automaticly computed in save() slug = models.SlugField(max_length=255, unique=True, help_text=squeeze(u""" Unique slug to identify the group used in urls. Automaticly computed from the group name. May not be changed manually. """)) # May be empty description = models.TextField(blank=True, help_text=squeeze(u""" Human readable group description. """)) # Backward relations: # # -- obligee_set: by Obligee.groups # May be empty # Indexes: # -- key: unique # -- name: unique # -- slug: unique objects = ObligeeGroupQuerySet.as_manager() @decorate(prevent_bulk_create=True) def save(self, *args, **kwargs): update_fields = kwargs.get(u'update_fields', None) # Generate and save slug if saving name if update_fields is None or u'name' in update_fields: self.slug = slugify(self.name) if update_fields is not None: update_fields.append(u'slug') super(ObligeeGroup, self).save(*args, **kwargs) def __unicode__(self): return u'[{}] {}'.format(self.pk, self.key)
class InforequestDraft(models.Model): # May NOT be NULL applicant = models.ForeignKey(User, help_text=squeeze(u""" The draft owner, the future inforequest applicant. """)) # May be NULL obligee = models.ForeignKey(u'obligees.Obligee', blank=True, null=True, help_text=squeeze(u""" The obligee the inforequest will be sent to, if the user has already set it. """)) # May be empty; Django migrations for MySQL backend are broken if ``default`` is mutable. subject = JSONField(blank=True, default=()) content = JSONField(blank=True, default=()) # May be empty attachment_set = generic.GenericRelation(u'attachments.Attachment', content_type_field=u'generic_type', object_id_field=u'generic_id') # Backward relations added to other models: # # -- User.inforequestdraft_set # May be empty # # -- Obligee.inforequestdraft_set # May be empty objects = InforequestDraftQuerySet.as_manager() class Meta: index_together = [ # [u'applicant'] -- ForeignKey defines index by default # [u'obligee'] -- ForeignKey defines index by default ] @staticmethod def prefetch_attachments(path=None, queryset=None): u""" Use to prefetch ``InforequestDraft.attachments`` """ if queryset is None: queryset = Attachment.objects.get_queryset() queryset = queryset.order_by_pk() return Prefetch(join_lookup(path, u'attachment_set'), queryset, to_attr=u'attachments') @cached_property def attachments(self): u""" Cached list of all inforequest draft attachments ordered by ``pk``. May be prefetched with ``prefetch_related(InforequestDraft.prefetch_attachments())`` queryset method. """ return list(self.attachment_set.order_by_pk()) def __unicode__(self): return u'%s' % self.pk
class Region(FormatMixin, models.Model): # "Kraj" # Primary key id = models.CharField(max_length=32, primary_key=True, help_text=squeeze(u""" Region primary key. Example: "SK031" (REGPJ.RSUJ3) """)) # Should NOT be empty name = models.CharField(max_length=255, unique=True, help_text=squeeze(u""" Unique human readable region name. (REGPJ.NAZKRJ, REGPJ.NAZRSUJ3) """)) # Should NOT be empty; Read-only; Automaticly computed in save() slug = models.SlugField(max_length=255, unique=True, help_text=squeeze(u""" Unique slug to identify the region used in urls. Automaticly computed from the region name. May not be changed manually. """)) # Backward relations: # # -- district_set: by District.region # May be empty # # -- municipality_set: by Municipality.region # May be empty # # -- neighbourhood_set: by Neighbourhood.region # May be empty # Indexes: # -- id: primary_key # -- name: unique # -- slug: unique objects = RegionQuerySet.as_manager() @decorate(prevent_bulk_create=True) def save(self, *args, **kwargs): update_fields = kwargs.get(u'update_fields', None) # Generate and save slug if saving name if update_fields is None or u'name' in update_fields: self.slug = slugify(self.name) if update_fields is not None: update_fields.append(u'slug') super(Region, self).save(*args, **kwargs) def __unicode__(self): return u'[{}] {}'.format(self.pk, self.name)
class InvitationAdminAddForm(forms.ModelForm): email = forms.CharField( validators=[ValidatorChain( validate_email, validate_unused_emails, )], widget=admin.widgets.AdminEmailInputWidget(), help_text=Invitation._meta.get_field(u'email').help_text, ) validity = forms.IntegerField( min_value=0, initial=app_settings.DEFAULT_VALIDITY, help_text=squeeze(u""" Number of days the invitation will be valid. """), ) invitor = Invitation._meta.get_field(u'invitor').formfield( widget=admin.widgets.ForeignKeyRawIdWidget( Invitation._meta.get_field(u'invitor').rel, admin.site), ) send_email = forms.BooleanField( initial=True, required=False, help_text=squeeze(u""" Check to send the invitation e-mail to the given address. Leave the checkbox empty if you just want to create a fake invitation without sending any real e-mail. For instance, if you want to send the invitation e-mail manually. """), ) def __init__(self, *args, **kwargs): user = kwargs.pop(u'user') super(InvitationAdminAddForm, self).__init__(*args, **kwargs) self.initial[u'invitor'] = user def save(self, commit=True): assert self.is_valid() invitation = Invitation.create( email=self.cleaned_data[u'email'], invitor=self.cleaned_data[u'invitor'], validity=self.cleaned_data[u'validity'], send_email=self.cleaned_data[u'send_email'], ) if commit: invitation.save() return invitation def save_m2m(self): pass
class ObligeeTag(FormatMixin, models.Model): # May NOT be empty key = models.CharField(max_length=255, unique=True, help_text=squeeze(u""" Unique key to identify the tag. Should contain only alphanumeric characters, underscores and hyphens. """)) # Should NOT be empty name = models.CharField(max_length=255, unique=True, help_text=squeeze(u""" Unique human readable tag name. """)) # Should NOT be empty; Read-only; Automaticly computed in save() slug = models.SlugField(max_length=255, unique=True, help_text=squeeze(u""" Unique slug to identify the tag used in urls. Automaticly computed from the tag name. May not be changed manually. """)) # Backward relations: # # -- obligee_set: by Obligee.tags # May be empty # Indexes: # -- key: unique # -- name: unique # -- slug: unique objects = ObligeeTagQuerySet.as_manager() @decorate(prevent_bulk_create=True) def save(self, *args, **kwargs): update_fields = kwargs.get(u'update_fields', None) # Generate and save slug if saving name if update_fields is None or u'name' in update_fields: self.slug = slugify(self.name) if update_fields is not None: update_fields.append(u'slug') super(ObligeeTag, self).save(*args, **kwargs) def __unicode__(self): return u'[{}] {}'.format(self.pk, self.key)
class Neighbourhood(FormatMixin, models.Model): # "Základná sídelná jednotka" # Primary key id = models.CharField(max_length=32, primary_key=True, help_text=squeeze(u""" Neighbourhood primary key. Example: "26289" (REGPJ.ICZSJ) """)) # Should NOT be empty name = models.CharField(max_length=255, help_text=squeeze(u""" Human readable neighbourhood name. Neighbourhood names are not unique, not even within a municipality. (REGPJ.NAZZSJ) """)) # Should NOT be empty cadastre = models.CharField(max_length=255, help_text=squeeze(u""" Human readable neighbourhood cadastre name. (REGPJ.NAZUTJ) """)) # May NOT be NULL municipality = models.ForeignKey(Municipality, help_text=u'Municipality the neighbourhood belongs to.') district = models.ForeignKey(District, help_text=u'District the neighbourhood belongs to.') region = models.ForeignKey(Region, help_text=u'Region the neighbourhood belongs to.') # Backward relations added to other models: # # -- Municipality.neighbourhood_set # May be empty # # -- District.neighbourhood_set # May be empty # # -- Region.neighbourhood_set # May be empty # Indexes: # -- id: primary_key # -- region: ForeignKey # -- district: ForeignKey # -- municipality: ForeignKey objects = NeighbourhoodQuerySet.as_manager() def __unicode__(self): return u'[{}] {}'.format(self.pk, self.name)
def handle(self, *prefixes, **options): groups = [ (u'CRITICALS', datacheck.CRITICAL, float(u'inf'), color_style().ERROR), (u'ERRORS', datacheck.ERROR, datacheck.CRITICAL, color_style().ERROR), (u'WARNINGS', datacheck.WARNING, datacheck.ERROR, color_style().WARNING), (u'INFOS', datacheck.INFO, datacheck.WARNING, color_style().NOTICE), (u'DEBUGS', 0, datacheck.INFO, color_style().NOTICE), ] output = [] if options[u'list']: output.append(u'Available data checks:') output.extend(u' -- {}'.format(c) for c in datacheck.registry) self.stdout.write(u'\n'.join(output)) return if prefixes: registry = datacheck.registry.filtered(prefixes) output.append(u'Running {} data checks:'.format(len(registry))) output.extend(u' -- {}'.format(c) for c in registry) output.append(u'') else: registry = datacheck.registry output.append(u'Running all registered data checks.') output.append(u'') issues = registry.run_checks( superficial=options[u'superficial'], autofix=options[u'autofix']) autofixable = len([s for s in issues if s.autofixable]) if autofixable: if options[u'autofix']: output.append(squeeze(u""" Data checks identified {} issues, {} of them were autofixed. """).format(len(issues), autofixable)) else: output.append(squeeze(u""" Data checks identified {} issues, {} of them can be autofixed (use --autofix). """).format(len(issues), autofixable)) else: output.append(u'Data checks identified {} issues.'.format(len(issues))) for group, level_min, level_max, style in groups: filtered = [a for a in issues if level_min <= a.level < level_max] if filtered: output.append(u'') output.append(u'{}:'.format(group)) output.extend(style(format(a)) for a in filtered) self.stdout.write(u'\n'.join(output))
def do_reset(self): count = Inforequest.objects.count() if count: inputed = self.importer.input_yes_no(squeeze(u""" Discarding current obligees will discard all existing inforequests as well. There are {} inforequests. """), u'Are you sure, you want to discard them?', count, default=u'N') if inputed != u'Y': raise CommandError(squeeze(u""" Existing inforequests prevented us from discarding current obligees. """)) self.reset_model(Obligee) self.reset_model(HistoricalObligee) self.reset_model(Inforequest)
def send_by_email(self): if not self.is_applicant_action: raise TypeError(u'{} is not applicant action'.format(self.get_type_display())) if not self.branch.collect_obligee_emails: # Django silently ignores messages with no recipients raise ValueError(u'Action has no recipients') sender_name = self.branch.inforequest.applicant_name sender_address = self.branch.inforequest.unique_email sender_formatted = formataddr((squeeze(sender_name), sender_address)) recipients = [formataddr(r) for r in self.branch.collect_obligee_emails] # FIXME: Attachment name and content type are set by client and not to be trusted. The name # must be sanitized and the content type white listed for known content types. Any unknown # content type should be replaced with 'application/octet-stream'. msg = EmailMessage(self.subject, self.content, sender_formatted, recipients) for attachment in self.attachments: msg.attach(attachment.name, attachment.content, attachment.content_type) msg.send() inforequestemail = InforequestEmail( inforequest=self.branch.inforequest, email=msg.instance, type=InforequestEmail.TYPES.APPLICANT_ACTION, ) inforequestemail.save() self.email = msg.instance self.save(update_fields=[u'email'])
def send_by_email(self): if not self.is_applicant_action: raise TypeError(u'{} is not applicant action'.format(self.get_type_display())) if not self.branch.collect_obligee_emails: # Django silently ignores messages with no recipients raise ValueError(u'Action has no recipients') sender_name = self.branch.inforequest.applicant_name sender_address = self.branch.inforequest.unique_email sender_formatted = formataddr((squeeze(sender_name), sender_address)) recipients = [formataddr(r) for r in self.branch.collect_obligee_emails] # FIXME: Attachment name is set by client and may not to be trusted. It must be sanitized. msg = EmailMessage(self.subject, self.content, sender_formatted, recipients) for attachment in self.attachments: msg.attach(attachment.name, attachment.content, attachment.content_type) msg.send() inforequestemail = InforequestEmail( inforequest=self.branch.inforequest, email=msg.instance, type=InforequestEmail.TYPES.APPLICANT_ACTION, ) inforequestemail.save() self.email = msg.instance self.save(update_fields=[u'email'])
def send_by_email(self): if not self.is_applicant_action: raise TypeError(u'%s is not applicant action' % self.get_type_display()) sender_name = self.branch.inforequest.applicant_name sender_address = self.branch.inforequest.unique_email sender_formatted = formataddr((squeeze(sender_name), sender_address)) recipients = (formataddr(r) for r in self.branch.collect_obligee_emails()) # FIXME: Attachment name and content type are set by client and not to be trusted. The name # must be sanitized and the content type white listed for known content types. Any unknown # content type should be replaced with 'application/octet-stream'. msg = EmailMessage(self.subject, self.content, sender_formatted, recipients) for attachment in self.attachments: msg.attach(attachment.name, attachment.content, attachment.content_type) msg.send() inforequestemail = InforequestEmail( inforequest=self.branch.inforequest, email=msg.instance, type=InforequestEmail.TYPES.APPLICANT_ACTION, ) inforequestemail.save() self.email = msg.instance self.save(update_fields=[u'email'])
def test_long_random_text(self): sample = u''.join( random.choice(string.printable if random.random() < 0.5 else string.whitespace) for i in range(1000)) res = squeeze(sample) self.assertRegexpMatches(res, r'^(\S+ )*\S+$')
def datachecks(superficial, autofix): u""" Checks that every ``Attachment`` instance has its file working, and there are not any orphaned attachment files. """ # This check is a bit slow. We skip it if running from cron or the user asked for # superficial tests only. if superficial: return attachments = Attachment.objects.all() attachment_names = {a.file.name for a in attachments} for attachment in attachments: try: try: attachment.file.open(u'rb') finally: attachment.file.close() except IOError: yield datacheck.Error(u'{} is missing its file: "{}".', attachment, attachment.file.name) field = Attachment._meta.get_field(u'file') if not field.storage.exists(field.upload_to): return for file_name in field.storage.listdir(field.upload_to)[1]: attachment_name = u'{}/{}'.format(field.upload_to, file_name) modified_time = utc_datetime_from_local(field.storage.modified_time(attachment_name)) timedelta = utc_now() - modified_time if timedelta > datetime.timedelta(days=5) and attachment_name not in attachment_names: yield datacheck.Info(squeeze(u""" There is no Attachment instance for file: "{}". The file is {} days old, so you can probably remove it. """), attachment_name, timedelta.days)
def values(self): res = super(ClarificationResponseStep, self).values() res[u'subject'] = squeeze( render_to_string( u'inforequests/clarification_response/forms/subject.txt')) res[u'content'] = self.fields[u'content'].finalize( self.cleaned_data[u'content']) return res
def test_long_text(self): res = squeeze(u""" Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. """) self.assertRegexpMatches(res, r'^(\S+ )*\S+$')
def clean(self): cleaned_data = super(NewActionCommonForm, self).clean() if not self.draft: if self.inforequest.has_undecided_emails: msg = squeeze(render_to_string(u'inforequests/messages/new_action-undecided_emails.txt', { u'inforequest': self.inforequest, })) raise forms.ValidationError(msg, code=u'undecided_emails') return cleaned_data
def pre_transition(self): res = super(PaperStep, self).pre_transition() if self.accessible: context = self.context(dict(finalize=True)) subject = squeeze(render_to_string(self.subject_template, context)) content = render_to_string(self.content_template, context) res.globals[self.subject_value_name] = subject res.globals[self.content_value_name] = content return res
def post_transition(self): res = super(Main, self).post_transition() if self.is_valid(): res.globals.update({ u'subject': squeeze(render_to_string( u'inforequests/clarification_response/forms/subject.txt')), u'content': self.fields[u'content'].finalize(self.cleaned_data[u'content']), }) return res
def clean(self): cleaned_data = super(AppealFinalStep, self).clean() if self.wizard.branch.inforequest.has_undecided_emails: msg = squeeze( render_to_string( u'inforequests/appeals/messages/undecided_emails.txt', { u'inforequest': self.wizard.branch.inforequest, })) raise forms.ValidationError(msg, code=u'undecided_emails') return cleaned_data
def test_only_whitespace(self): self.assertEqual(squeeze(u' '), u'') self.assertEqual(squeeze(u' '), u'') self.assertEqual(squeeze(u' \n\n \n\t\t\r\r\r\n\n'), u'') self.assertEqual(squeeze(u'\n\n\n\n\n\n\n\n\n\n'), u'') self.assertEqual(squeeze(u'\t\t\t\t'), u'') self.assertEqual(squeeze(u'\r'), u'')
class InforequestEmail(FormatMixin, models.Model): # May NOT be NULL; m2m ends; Indexes are prefixes of [inforequest, email] and # [email, inforequest] indexes, respectively inforequest = models.ForeignKey(u'Inforequest', db_index=False) email = models.ForeignKey(u'mail.Message', db_index=False) # May NOT be NULL TYPES = FieldChoices( # For outbound messages (u'APPLICANT_ACTION', 1, _(u'inforequests:InforequestEmail:type:APPLICANT_ACTION')), # For inbound messages (u'OBLIGEE_ACTION', 2, _(u'inforequests:InforequestEmail:type:OBLIGEE_ACTION')), (u'UNDECIDED', 3, _(u'inforequests:InforequestEmail:type:UNDECIDED')), (u'UNRELATED', 4, _(u'inforequests:InforequestEmail:type:UNRELATED')), (u'UNKNOWN', 5, _(u'inforequests:InforequestEmail:type:UNKNOWN')), ) type = models.SmallIntegerField(choices=TYPES._choices, help_text=squeeze(u""" "Applicant Action": the email represents an applicant action; "Obligee Action": the email represents an obligee action; "Undecided": The email is waiting for applicant decision; "Unrelated": Marked as an unrelated email; "Unknown": Marked as an email the applicant didn't know how to decide. It must be "Applicant Action" for outbound mesages or one of the remaining values for inbound messages. """ )) # Backward relations added to other models: # # -- Inforequest.inforequestemail_set # May be empty # # -- Message.inforequestemail_set # May be empty # Indexes: # -- email, inforequest: index_together # -- inforequest, email: index_together # -- type, inforequest: index_together objects = InforequestEmailQuerySet.as_manager() class Meta: index_together = [ [u'email', u'inforequest'], [u'inforequest', u'email'], [u'type', u'inforequest'], ] def __unicode__(self): return format(self.pk)
class LoadSheetsCommand(BaseCommand): help = u'Loads .xlsx files with data' args = u'file [file ...]' option_list = BaseCommand.option_list + ( make_option(u'--dry-run', action=u'store_true', default=False, help=squeeze(u""" Just show if the files would be imported correctly. Rollback all changes at the end. """)), make_option(u'--reset', action=u'store_true', default=False, help=squeeze(u""" Discard current data before imporing the files. Only data from sheets present in the files are discarded. Data from missing sheets are left untouched. """)), make_option(u'--assume', choices=[u'yes', u'no', u'default'], help=squeeze(u""" Assume yes/no/default answer to all yes/no questions. """)), ) importer = Importer book = None def handle(self, *args, **options): if not args: raise CommandError(u'No file specified.') try: importer = self.importer(self.book, options, self.stdout) importer.do_import(args) except (KeyboardInterrupt, EOFError): self.stdout.write(u'\n') raise CommandError(u'Aborted') except RollbackDryRun: pass
def values(self): res = super(WizardPaperStep, self).values() subject = squeeze( render_to_string(self.subject_template, self.context(dict(finalize=True)))) content = render_to_string(self.content_template, self.context(dict(finalize=True))) res.update({ self.subject_value_name: subject, self.content_value_name: content, }) return res
def post_transition(self): res = super(Main, self).post_transition() if self.is_valid(): res.globals.update({ u'subject': squeeze( render_to_string( u'inforequests/clarification_response/forms/subject.txt' )), u'content': self.fields[u'content'].finalize( self.cleaned_data[u'content']), }) return res
def render_mail(template_prefix, dictionary=None, **kwargs): u""" Create ``django.core.mail.EmailMessage`` object ready to be sent with ``msg.send()`` method. Message subject and body are rendered using templates "(prefix)_subject.txt" and "(prefix)_message.txt" and/or "(prefix)_message.html". If both ".txt" and ".html" body templates exist, the created message is multipart/alternativea including its text and html versions. The functions accepts additional keyword arguments for EmailMessage constructor. Of most interest are: ``from_email``, ``to``, ``bcc``, ``attachments``, ``headers`` and ``cc``. Based on: Django-allauth's allauth.DefaultAccountAdapter.render_mail method. Examples: render_mail('app/mail', from_email='My Name <*****@*****.**>', to=['Your Name <*****@*****.**>']) """ site = Site.objects.get_current() subject = render_to_string(u'{}_subject.txt'.format(template_prefix), dictionary) subject = squeeze(u'[{}] {}'.format(site.name, subject)) bodies = {} for ext in [u'html', u'txt']: template_name = u'{}_message.{}'.format(template_prefix, ext) try: bodies[ext] = render_to_string(template_name, dictionary).strip() except TemplateDoesNotExist: # We need at least one body if ext == u'txt' and not bodies: raise if u'txt' in bodies and u'html' in bodies: msg = EmailMultiAlternatives(subject, bodies[u'txt'], **kwargs) msg.attach_alternative(bodies[u'html'], u'text/html') elif u'html' in bodies: msg = EmailMessage(subject, bodies[u'html'], **kwargs) msg.content_subtype = u'html' # Main content is now text/html else: msg = EmailMessage(subject, bodies[u'txt'], **kwargs) return msg
def __init__(self, page, file, create, *args, **kwargs): super(FileEditForm, self).__init__(*args, **kwargs) self.page = page self.file = file self.create = create self.fieldsets = [ Bunch(label=None, collapse=False, fields=[]), ] self.fieldsets[0].fields.append( FakeField(u'Page', LivePath.render(page.lang, page.path))) self.fields[u'name'] = forms.CharField( label=u'File Name', validators=[ RegexValidator( pages.file_regex, squeeze(u""" Enter a valid file name. Only letters, numbers, dashes and dots are allowed. """)), ], ) if not create: self.initial[u'name'] = file.name self.fieldsets[0].fields.append(self[u'name']) if not create: self.fieldsets[0].fields.append( FakeField(u'Content Type', file.content_type)) self.fieldsets[0].fields.append( FakeField( u'Modified', date_format(datetime.datetime.fromtimestamp(file.mtime), u'DATETIME_FORMAT'))) self.fields[u'content'] = forms.FileField( label=u'Content', widget=PreviewFileInput, ) if not create: self.initial[u'content'] = file self.fieldsets[0].fields.append(self[u'content'])
def __init__(self, page, file, create, *args, **kwargs): super(FileEditForm, self).__init__(*args, **kwargs) self.page = page self.file = file self.create = create self.fieldsets = [ Bunch(label=None, collapse=False, fields=[]), ] self.fieldsets[0].fields.append(FakeField(u'Page', LivePath.render(page.lang, page.path))) self.fields[u'name'] = forms.CharField( label=u'File Name', validators=[ RegexValidator(pages.file_regex, squeeze(u""" Enter a valid file name. Only letters, numbers, dashes and dots are allowed. """)), ], ) if not create: self.initial[u'name'] = file.name self.fieldsets[0].fields.append(self[u'name']) if not create: self.fieldsets[0].fields.append(FakeField(u'Content Type', file.content_type)) self.fieldsets[0].fields.append(FakeField(u'Modified', date_format(datetime.datetime.fromtimestamp(file.mtime), u'DATETIME_FORMAT'))) self.fields[u'content'] = forms.FileField( label=u'Content', widget=PreviewFileInput, ) if not create: self.initial[u'content'] = file self.fieldsets[0].fields.append(self[u'content'])
class Branch(FormatMixin, models.Model): # May NOT be NULL; Index is prefix of [inforequest, advanced_by] index inforequest = models.ForeignKey(u'Inforequest', db_index=False) # May NOT be NULL obligee = models.ForeignKey( u'obligees.Obligee', help_text=u'The obligee the inforequest was sent or advanced to.') # May NOT be NULL; Automaticly frozen in save() when creating a new object historicalobligee = models.ForeignKey(u'obligees.HistoricalObligee', help_text=squeeze(u""" Frozen Obligee at the time the Inforequest was submitted or advanced to it. """)) # Advancement action that advanced the inforequest to this obligee; None if it's inforequest # main branch. Inforequest must contain exactly one branch with ``advanced_by`` set to None; # Index is prefix of [advanced_by, inforequest] index advanced_by = models.ForeignKey(u'Action', related_name=u'advanced_to_set', blank=True, null=True, db_index=False, help_text=squeeze(u""" NULL for main branches. The advancement action the inforequest was advanced by for advanced branches. Every Inforequest must contain exactly one main branch. """)) # Backward relations: # # -- action_set: by Action.branch # May NOT be empty; The first action of every main branch must be REQUEST and the first # action of every advanced branch ADVANCED_REQUEST. # Backward relations added to other models: # # -- Inforequest.branch_set # May NOT be empty # # -- Obligee.branch_set # May be empty # # -- HistoricalObligee.branch_set # May be empty # # -- Action.advanced_to_set # May be empty # Indexes: # -- inforequest, advanced_by: index_together # -- advanced_by, inforequest: index_together # -- obligee: ForeignKey # -- historicalobligee: ForeignKey objects = BranchQuerySet.as_manager() class Meta: verbose_name_plural = u'Branches' index_together = [ [u'inforequest', u'advanced_by'], [u'advanced_by', u'inforequest'], ] @cached_property def is_main(self): return self.advanced_by_id is None @staticmethod def prefetch_actions(path=None, queryset=None): u""" Use to prefetch ``Branch.actions``. """ if queryset is None: queryset = Action.objects.get_queryset() queryset = queryset.order_by_created() return Prefetch(join_lookup(path, u'action_set'), queryset, to_attr=u'actions') @cached_property def actions(self): u""" Cached list of all branch actions ordered by ``created``. The list should not be empty. May be prefetched with ``prefetch_related(Branch.prefetch_actions())`` queryset method. """ return list(self.action_set.order_by_created()) @staticmethod def prefetch_actions_by_email(path=None, queryset=None): u""" Use to prefetch ``Branch.actions_by_email``. """ if queryset is None: queryset = Action.objects.get_queryset() queryset = queryset.by_email() queryset = queryset.order_by_created() return Prefetch(join_lookup(path, u'action_set'), queryset, to_attr=u'actions_by_email') @cached_property def actions_by_email(self): u""" Cached list of all branch actions sent by email ordered by ``created``. May be prefetched with ``prefetch_related(Branch.prefetch_actions_by_email())`` queryset method. Takes advantage of ``Branch.actions`` if it is fetched already. """ if u'actions' in self.__dict__: return list(a for a in self.actions if a.is_by_email) else: return list(self.action_set.by_email().order_by_created()) @staticmethod def prefetch_last_action(path=None, queryset=None): u""" Use to prefetch ``Branch.last_action``. Redundant if ``prefetch_actions()`` is already used. """ if queryset is None: queryset = Action.objects.get_queryset() quote_name = connection.ops.quote_name queryset = queryset.extra(where=[ u'{action}.{pk} = (' u'SELECT p.{pk} ' u'FROM {action} p ' u'WHERE p.{branch} = {action}.{branch} ' u'ORDER BY p.{created} DESC, p.{pk} DESC ' u'LIMIT 1' u')'.format( action=quote_name(Action._meta.db_table), pk=quote_name(Action._meta.pk.column), branch=quote_name(Action._meta.get_field(u'branch').column), created=quote_name(Action._meta.get_field(u'created').column), ) ]) return Prefetch(join_lookup(path, u'action_set'), queryset, to_attr=u'_last_action') @cached_property def last_action(self): u""" Cached last branch action. Returns None if the branch has no actions. May be prefetched with ``prefetch_related(Branch.prefetch_last_action())`` queryset method. Takes advantage of ``Branch.actions`` if it is fetched already. """ if u'_last_action' in self.__dict__: try: return self._last_action[0] except IndexError: return None elif u'actions' in self.__dict__: try: return self.actions[-1] except IndexError: return None else: return self.action_set.order_by_created().last() @cached_property def can_add_request(self): return False @cached_property def can_add_clarification_response(self): return (self.last_action.type == Action.TYPES.CLARIFICATION_REQUEST and self.last_action.deadline.calendar_days_behind <= 3) @cached_property def can_add_appeal(self): if self.last_action.type in [ Action.TYPES.REQUEST, Action.TYPES.CLARIFICATION_RESPONSE, Action.TYPES.CONFIRMATION, Action.TYPES.EXTENSION, Action.TYPES.REMANDMENT, Action.TYPES.ADVANCED_REQUEST, ]: # All these actions have deadlines return self.last_action.deadline.is_deadline_missed if self.last_action.type == Action.TYPES.ADVANCEMENT: # Advancement has no deadline defined return (local_today() - self.last_action.delivered_date).days <= 7 if self.last_action.type == Action.TYPES.REFUSAL: return self.last_action.deadline.calendar_days_behind <= 7 if self.last_action.type == Action.TYPES.DISCLOSURE: return (self.last_action.disclosure_level != Action.DISCLOSURE_LEVELS.FULL and self.last_action.deadline.calendar_days_behind <= 47) if self.last_action.type == Action.TYPES.EXPIRATION: return self.last_action.deadline.calendar_days_behind <= 47 return False @cached_property def can_add_confirmation(self): return self.last_action.type in [ Action.TYPES.REQUEST, Action.TYPES.ADVANCED_REQUEST, ] @cached_property def can_add_extension(self): return self.last_action.type in [ Action.TYPES.REQUEST, Action.TYPES.CONFIRMATION, Action.TYPES.CLARIFICATION_RESPONSE, Action.TYPES.REMANDMENT, Action.TYPES.ADVANCED_REQUEST, ] @cached_property def can_add_advancement(self): return self.last_action.type in [ Action.TYPES.REQUEST, Action.TYPES.CLARIFICATION_RESPONSE, Action.TYPES.CONFIRMATION, Action.TYPES.ADVANCED_REQUEST, ] @cached_property def can_add_clarification_request(self): return self.last_action.type in [ Action.TYPES.REQUEST, Action.TYPES.CLARIFICATION_RESPONSE, Action.TYPES.CONFIRMATION, Action.TYPES.CLARIFICATION_REQUEST, Action.TYPES.ADVANCED_REQUEST, ] @cached_property def can_add_disclosure(self): return self.last_action.type in [ Action.TYPES.REQUEST, Action.TYPES.CLARIFICATION_RESPONSE, Action.TYPES.CONFIRMATION, Action.TYPES.EXTENSION, Action.TYPES.REMANDMENT, Action.TYPES.ADVANCED_REQUEST, ] @cached_property def can_add_refusal(self): return self.last_action.type in [ Action.TYPES.REQUEST, Action.TYPES.CLARIFICATION_RESPONSE, Action.TYPES.CONFIRMATION, Action.TYPES.EXTENSION, Action.TYPES.REMANDMENT, Action.TYPES.ADVANCED_REQUEST, ] @cached_property def can_add_affirmation(self): return self.last_action.type == Action.TYPES.APPEAL @cached_property def can_add_reversion(self): return self.last_action.type == Action.TYPES.APPEAL @cached_property def can_add_remandment(self): return self.last_action.type == Action.TYPES.APPEAL @cached_property def can_add_applicant_action(self): return self.can_add_action(*Action.APPLICANT_ACTION_TYPES) @cached_property def can_add_applicant_email_action(self): return self.can_add_action(*Action.APPLICANT_EMAIL_ACTION_TYPES) @cached_property def can_add_obligee_action(self): return self.can_add_action(*Action.OBLIGEE_ACTION_TYPES) @cached_property def can_add_obligee_email_action(self): return self.can_add_action(*Action.OBLIGEE_EMAIL_ACTION_TYPES) def can_add_action(self, *action_types): for action_type in action_types: type_name = Action.TYPES._inverse[action_type] if getattr(self, u'can_add_{}'.format(type_name.lower())): return True return False @classmethod def create(cls, *args, **kwargs): action_args = kwargs.pop(u'action_args', None) or [] action_kwargs = kwargs.pop(u'action_kwargs', None) or {} branch = Branch(*args, **kwargs) assert (action_kwargs[u'type'] in [ Action.TYPES.REQUEST, Action.TYPES.ADVANCED_REQUEST ], u'Branch must be created with a request or an advanced request action' ) @after_saved(branch) def deferred(branch): action = Action.create(branch=branch, *action_args, **action_kwargs) action.save() return branch @decorate(prevent_bulk_create=True) def save(self, *args, **kwargs): if self.pk is None: # Creating a new object assert self.obligee_id is not None, u'Branch.obligee is mandatory' assert self.historicalobligee_id is None, u'Branch.historicalobligee is read-only' self.historicalobligee = self.obligee.history.first() super(Branch, self).save(*args, **kwargs) def add_expiration_if_expired(self): if not self.last_action.has_obligee_deadline_missed: return if self.last_action.type == Action.TYPES.APPEAL: action_type = Action.TYPES.APPEAL_EXPIRATION else: action_type = Action.TYPES.EXPIRATION expiration = Action.create( branch=self, type=action_type, legal_date=self.last_action.deadline.deadline_date, ) expiration.save() @cached_property def collect_obligee_emails(self): res = {} for action in self.actions_by_email: if action.email.type == action.email.TYPES.INBOUND: res.update({action.email.from_mail: action.email.from_name}) else: # OUTBOUND res.update({r.mail: r.name for r in action.email.recipients}) # Current obligee emails res.update({mail: name for name, mail in self.obligee.emails_parsed}) return [(name, mail) for mail, name in res.items()] def __unicode__(self): return format(self.pk)
def send_message(self, message): assert message.type == message.TYPES.OUTBOUND assert message.processed is None # Based on djrill.mail.backends.DjrillBackend; We can't use Djrill directly because it # sends the mail synchronously during user requests. msg = {} msg[u'subject'] = message.subject msg[u'from_email'] = message.from_mail if message.from_name: msg[u'from_name'] = message.from_name if message.html: msg[u'html'] = message.html if message.text: msg[u'text'] = message.text if message.headers: msg[u'headers'] = message.headers msg[u'to'] = [] recipients = defaultdict(list) for recipient in message.recipients: rcp = {} rcp[u'email'] = recipient.mail if recipient.name: rcp[u'name'] = recipient.name if recipient.type == recipient.TYPES.TO: rcp[u'type'] = u'to' elif recipient.type == recipient.TYPES.CC: rcp[u'type'] = u'cc' else: assert recipient.type == recipient.TYPES.BCC rcp[u'type'] = u'bcc' msg[u'to'].append(rcp) recipients[recipient.mail].append(recipient) msg[u'attachments'] = [] for attachment in message.attachments: attch = {} attch[u'type'] = attachment.content_type attch[u'name'] = attachment.name attch[u'content'] = base64.b64encode(attachment.content) msg[u'attachments'].append(attch) data = {} data[u'key'] = self.api_key data[u'message'] = msg response = requests.post(self.api_send, data=json.dumps(data)) if response.status_code != 200: raise RuntimeError(squeeze(u""" Sending Message(pk={}) failed with status code {}. Mandrill response: {} """).format(message.pk, response.status_code, response.text)) for rcp in response.json(): for recipient in recipients[rcp[u'email']]: recipient.remote_id = rcp[u'_id'] if rcp[u'status'] == u'sent': recipient.status = recipient.STATUSES.SENT elif rcp[u'status'] in [u'queued', u'scheduled']: recipient.status = recipient.STATUSES.QUEUED elif rcp[u'status'] == u'rejected': recipient.status = recipient.STATUSES.REJECTED recipient.status_details = rcp[u'reject_reason'] elif rcp[u'status'] == u'invalid': recipient.status = recipient.STATUSES.INVALID else: recipient.status = recipient.STATUSES.UNDEFINED recipient.save(update_fields=[u'remote_id', u'status', u'status_details'])
def test_with_only_untranslated_template(self): # Existing: first.html # Missing: first.de.html with translation(u'de'): rendered = squeeze(render_to_string(u'first.html')) self.assertEqual(rendered, u'(first.html)')
def test_translated_template_has_priority(self): # Existing: first.html, first.en.html with translation(u'en'): rendered = squeeze(render_to_string(u'first.html')) self.assertEqual(rendered, u'(first.en.html)')
def finalize_subject(self): step = self.steps[u'paper'] return squeeze(render_to_string(step.subject_template, step.context(dict(finalize=True))))
def test_trim(self): self.assertEqual(squeeze(u' left'), u'left') self.assertEqual(squeeze(u'right '), u'right') self.assertEqual(squeeze(u' both '), u'both') self.assertEqual(squeeze(u'\n\n\n111\n\n\n'), u'111') self.assertEqual(squeeze(u' aaa fff rrr '), u'aaa fff rrr')
def test_linebreaks_and_tabs(self): self.assertEqual(squeeze(u'\n\n1\t2\r\r3\r\n4\n\r5\r\r6\n\n7'), u'1 2 3 4 5 6 7') self.assertEqual(squeeze(u'1 \n2\n\n 3 \t 4\n\n\n\n\n\n\n\n\n5'), u'1 2 3 4 5')
def test_empty_string(self): self.assertEqual(squeeze(u''), u'')
def test_long_random_text(self): sample = u''.join(random.choice(string.printable if random.random() < 0.5 else string.whitespace) for i in range(1000)) res = squeeze(sample) self.assertRegexpMatches(res, r'^(\S+ )*\S+$')
class Recipient(FormatMixin, models.Model): # May NOT be NULL message = models.ForeignKey(u'Message') # May be empty name = models.CharField(blank=True, max_length=255, help_text=escape( squeeze(u""" Recipient full name. For instance setting name to "John Smith" and e-mail to "*****@*****.**" will send the message to "John Smith <*****@*****.**>". """))) # Should NOT be empty mail = models.EmailField( max_length=255, help_text=u'Recipient e-mail address, e.g. "*****@*****.**".') # May NOT be NULL TYPES = FieldChoices( (u'TO', 1, _(u'mail:Recipient:type:TO')), (u'CC', 2, _(u'mail:Recipient:type:CC')), (u'BCC', 3, _(u'mail:Recipient:type:BCC')), ) type = models.SmallIntegerField( choices=TYPES._choices, help_text=u'Recipient type: To, Cc, or Bcc.') # May NOT be NULL STATUSES = FieldChoices( # For inbound messages (u'INBOUND', 8, _(u'mail:Recipient:status:INBOUND')), # For outbound messages (u'UNDEFINED', 1, _(u'mail:Recipient:status:UNDEFINED')), (u'QUEUED', 2, _(u'mail:Recipient:status:QUEUED')), (u'REJECTED', 3, _(u'mail:Recipient:status:REJECTED')), (u'INVALID', 4, _(u'mail:Recipient:status:INVALID')), (u'SENT', 5, _(u'mail:Recipient:status:SENT')), (u'DELIVERED', 6, _(u'mail:Recipient:status:DELIVERED')), (u'OPENED', 7, _(u'mail:Recipient:status:OPENED')), ) INBOUND_STATUSES = (STATUSES.INBOUND, ) OUTBOUND_STATUSES = ( STATUSES.UNDEFINED, STATUSES.QUEUED, STATUSES.REJECTED, STATUSES.INVALID, STATUSES.SENT, STATUSES.DELIVERED, STATUSES.OPENED, ) status = models.SmallIntegerField(choices=STATUSES._choices, help_text=squeeze(u""" Delivery status for the message recipient. It must be "Inbound" for inbound mesages or one of the remaining statuses for outbound messages. """)) # May be empty status_details = models.CharField(blank=True, max_length=255, help_text=squeeze(u""" Unspecific delivery status details set by e-mail transport. Leave blank if not sure. """)) # May be empty remote_id = models.CharField(blank=True, max_length=255, db_index=True, help_text=squeeze(u""" Recipient reference ID set by e-mail transport. Leave blank if not sure. """)) # Backward relations added to other models: # # -- Message.recipient_set # Should NOT be empty # Indexes: # -- message: ForeignKey # -- remote_id: on field objects = RecipientQuerySet.as_manager() @property def formatted(self): return formataddr((self.name, self.mail)) @formatted.setter def formatted(self, value): self.name, self.mail = parseaddr(value) def __unicode__(self): return u'[{}] {}'.format(self.pk, self.mail)
class Message(FormatMixin, models.Model): # May NOT be NULL TYPES = FieldChoices( (u'INBOUND', 1, _(u'mail:Message:type:INBOUND')), (u'OUTBOUND', 2, _(u'mail:Message:type:OUTBOUND')), ) type = models.SmallIntegerField(choices=TYPES._choices) # May NOT be NULL created = models.DateTimeField(auto_now_add=True, help_text=squeeze(u""" Date and time the message object was created, """)) # NOT NULL for processed messages; NULL for queued messages processed = models.DateTimeField(blank=True, null=True, help_text=squeeze(u""" Date and time the message was sent or received and processed. Leave blank if you want the application to process it. """)) # May be empty from_name = models.CharField(blank=True, max_length=255, help_text=escape( squeeze(u""" Sender full name. For instance setting name to "John Smith" and e-mail to "*****@*****.**" will set the sender address to "John Smith <*****@*****.**>". """))) # Should NOT be empty from_mail = models.EmailField(max_length=255, help_text=squeeze(u""" Sender e-mail address, e.g. "*****@*****.**". """)) # May be empty for inbound messages; Empty for outbound messages received_for = models.EmailField(blank=True, max_length=255, help_text=squeeze(u""" The address we received the massage for. It may, but does not have to be among the message recipients, as the address may have heen bcc-ed to. The address is empty for all outbound messages. It may also be empty for inbound messages if we don't know it, or the used mail transport does not support it. """)) # May be empty subject = models.CharField(blank=True, max_length=255) # May be empty text = models.TextField(blank=True, help_text=squeeze(u""" "text/plain" message body alternative. """)) html = models.TextField(blank=True, help_text=squeeze(u""" "text/html" message body alternative. """)) # Dict: String->(String|[String]); May be empty headers = JSONField(blank=True, default={}, help_text=squeeze(u""" Dictionary mapping header names to their values, or lists of their values. For outbound messages it contains only extra headers added by the sender. For inbound messages it contains all message headers. """)) # May be empty; Backward generic relation attachment_set = generic.GenericRelation( u'attachments.Attachment', content_type_field=u'generic_type', object_id_field=u'generic_id') # Backward relations: # # -- recipient_set: by Recipient.message # Should NOT be empty # Indexes: # -- processed, id: index_together # -- created, id: index_together objects = MessageQuerySet.as_manager() class Meta: index_together = [ [u'processed', u'id'], [u'created', u'id'], ] @property def from_formatted(self): return formataddr((self.from_name, self.from_mail)) @from_formatted.setter def from_formatted(self, value): self.from_name, self.from_mail = parseaddr(value) @staticmethod def prefetch_attachments(path=None, queryset=None): u""" Use to prefetch ``Message.attachments``. """ if queryset is None: queryset = Attachment.objects.get_queryset() queryset = queryset.order_by_pk() return Prefetch(join_lookup(path, u'attachment_set'), queryset, to_attr=u'attachments') @cached_property def attachments(self): u""" Cached list of all message attachments ordered by ``pk``. May be prefetched with ``prefetch_related(Message.prefetch_attachments())`` queryset method. """ return list(self.attachment_set.order_by_pk()) @staticmethod def prefetch_recipients(path=None, queryset=None): u""" Use to prefetch ``Message.recipients``. """ if queryset is None: queryset = Recipient.objects.get_queryset() queryset = queryset.order_by_pk() return Prefetch(join_lookup(path, u'recipient_set'), queryset, to_attr=u'recipients') @cached_property def recipients(self): u""" Cached list of all message recipients ordered by ``pk``. May be prefetched with ``prefetch_related(Message.prefetch_recipients())`` queryset method. """ return list(self.recipient_set.order_by_pk()) @cached_property def recipients_to(self): u""" Cached list of all message "to" recipients ordered by ``pk``. Takes advantage of ``Message.recipients`` if it is already fetched. """ if u'recipients' in self.__dict__: return list(r for r in self.recipients if r.type == Recipient.TYPES.TO) else: return list(self.recipient_set.to().order_by_pk()) @cached_property def recipients_cc(self): u""" Cached list of all message "cc" recipients ordered by ``pk``. Takes advantage of ``Message.recipients`` if it is already fetched. """ if u'recipients' in self.__dict__: return list(r for r in self.recipients if r.type == Recipient.TYPES.CC) else: return list(self.recipient_set.cc().order_by_pk()) @cached_property def recipients_bcc(self): u""" Cached list of all message "bcc" recipients ordered by ``pk``. Takes advantage of ``Message.recipients`` if it is already fetched. """ if u'recipients' in self.__dict__: return list(r for r in self.recipients if r.type == Recipient.TYPES.BCC) else: return list(self.recipient_set.bcc().order_by_pk()) @cached_property def to_formatted(self): return u', '.join(r.formatted for r in self.recipients_to) @cached_property def cc_formatted(self): return u', '.join(r.formatted for r in self.recipients_cc) @cached_property def bcc_formatted(self): return u', '.join(r.formatted for r in self.recipients_bcc) def __unicode__(self): return format(self.pk)
def __init__(self, page, create, *args, **kwargs): super(PageEditForm, self).__init__(*args, **kwargs) self.page = page self.create = create edit_root = not create and page.is_root edit_redirect = not create and page.is_redirect edit_regular = not create and not page.is_root and not page.is_redirect assert bool(create) + bool(edit_root) + bool(edit_redirect) + bool(edit_regular) == 1 self.fieldsets = [ Bunch(label=None, collapse=False, fields=[]), Bunch(label=u'Raw Config', collapse=True, fields=[]), Bunch(label=u'Page Content', collapse=False, fields=[]), ] if edit_regular: self.fields[u'parent'] = forms.CharField( label=u'URL Path', validators=[ RegexValidator(pages.path_regex, squeeze(u""" Enter a valid path. It must be an absolute path with respect to the root page starting and ending with a slash. """)), ], widget=forms.TextInput(attrs={ u'class': u'popup-path', u'style': u'width: 50em;', u'data-popup-url': reverse(u'admin:pages_index', args=[page.lang]), u'data-icon': staticfiles_storage.url(u'admin/img/selector-search.gif'), }), ) self.initial[u'parent'] = page.ppath self.fieldsets[0].fields.append(self[u'parent']) self.fieldsets[0].fields.append(LivePath(page.lang, self[u'parent'])) else: self.fieldsets[0].fields.append(FakeField(u'URL Path', page.path)) if create or edit_regular: self.fields[u'name'] = forms.CharField( label=u'URL Name', validators=[ RegexValidator(pages.slug_regex, u'Enter a valid slug. Only letters, numbers and dashes are allowed.'), ], ) if not create: self.initial[u'name'] = page.name self.fieldsets[0].fields.append(self[u'name']) if create or edit_root or edit_regular: self.fields[u'title'] = forms.CharField( label=u'Page Title', required=False, widget=forms.TextInput(attrs={ u'style': u'width: 50em;', }), ) if not create: self.initial[u'title'] = page._config.get(u'title') self.fieldsets[0].fields.append(self[u'title']) if create or edit_root or edit_regular: self.fields[u'label'] = forms.CharField( label=u'Menu Label', required=False, ) if not create: self.initial[u'label'] = page._config.get(u'label') self.fieldsets[0].fields.append(self[u'label']) if create or edit_regular: self.fields[u'order'] = forms.CharField( label=u'Sort Key', required=False, ) if not create: self.initial[u'order'] = page._config.get(u'order') self.fieldsets[0].fields.append(self[u'order']) if create or edit_regular: for lang, _ in settings.LANGUAGES: if lang != page.lang: key = u'lang_{}'.format(lang) self.fields[key] = forms.CharField( label=u'Translation {}'.format(lang.upper()), required=False, validators=[ RegexValidator(pages.path_regex, squeeze(u""" Enter a valid path. It must be an absolute path with respect to the root page starting and ending with a slash. """)), ], widget=forms.TextInput(attrs={ u'class': u'popup-path', u'style': u'width: 50em;', u'data-popup-url': reverse(u'admin:pages_index', args=[lang]), u'data-icon': staticfiles_storage.url(u'admin/img/selector-search.gif'), }), ) if not create: self.initial[key] = page._config.get(key) self.fieldsets[0].fields.append(self[key]) self.fieldsets[0].fields.append(LivePath(lang, self[key])) if edit_redirect: self.fields[u'redirect'] = forms.CharField( label=u'Redirect', validators=[ RegexValidator(pages.path_regex, squeeze(u""" Enter a valid path. It must be an absolute path with respect to the root page starting and ending with a slash. """)), ], widget=forms.TextInput(attrs={ u'class': u'popup-path', u'style': u'width: 50em;', u'data-popup-url': reverse(u'admin:pages_index', args=[page.lang]), u'data-icon': staticfiles_storage.url(u'admin/img/selector-search.gif'), }), ) self.initial[u'redirect'] = page.redirect_path self.fieldsets[0].fields.append(self[u'redirect']) self.fieldsets[0].fields.append(LivePath(page.lang, self[u'redirect'])) if create or edit_root or edit_regular: self.fields[u'disabled'] = forms.BooleanField( label=u'Disabled', required=False, ) if not create: self.initial[u'disabled'] = bool(page._config.get(u'disabled')) self.fieldsets[0].fields.append(self[u'disabled']) if create or edit_root or edit_regular: self.fields[u'raw'] = forms.CharField( label=u'', required=False, widget=forms.Textarea(attrs={ u'style': u'width: 100%; height: 10em;', }), ) if not create: self.initial[u'raw'] = page.raw_config self.fieldsets[1].fields.append(self[u'raw']) if create or edit_root or edit_regular: with translation(page.lang): url = reverse(u'admin:pages_preview') self.fields[u'template'] = forms.CharField( label=u'', required=False, widget=forms.Textarea(attrs={ u'class': u'template-widget', u'data-url': url, }), ) if not create: self.initial[u'template'] = page.template self.fieldsets[2].fields.append(self[u'template'])