class Snippet(FileResource): source = FileField(upload_to='snippets')
class Topic(BasePage): resource_type = "topic" parent_page_types = ["Topics"] subpage_types = ["Topic"] template = "topic.html" # Content fields description = RichTextField( blank=True, default="", features=RICH_TEXT_FEATURES_SIMPLE, help_text="Optional short text description, max. 400 characters", max_length=400, ) featured = StreamField( StreamBlock( [ ( "article", PageChooserBlock( target_model=( "articles.Article", "externalcontent.ExternalArticle", ) ), ), ("external_page", FeaturedExternalBlock()), ], max_num=4, required=False, ), null=True, blank=True, help_text="Optional space for featured articles, max. 4", ) tabbed_panels_title = CharField(max_length=250, blank=True, default="") tabbed_panels = StreamField( StreamBlock([("panel", TabbedPanelBlock())], max_num=3, required=False), null=True, blank=True, help_text="Optional tabbed panels for linking out to other resources, max. 3", verbose_name="Tabbed panels", ) latest_articles_count = IntegerField( choices=RESOURCE_COUNT_CHOICES, default=3, help_text="The number of articles to display for this topic.", ) # Card fields card_title = CharField("Title", max_length=140, blank=True, default="") card_description = TextField("Description", max_length=400, blank=True, default="") card_image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", verbose_name="Image", ) # Meta icon = FileField(upload_to="topics/icons", blank=True, default="") color = CharField(max_length=14, choices=COLOR_CHOICES, default="blue-40") keywords = ClusterTaggableManager(through=TopicTag, blank=True) # Content panels content_panels = BasePage.content_panels + [ FieldPanel("description"), StreamFieldPanel("featured"), FieldPanel("tabbed_panels_title"), StreamFieldPanel("tabbed_panels"), FieldPanel("latest_articles_count"), MultiFieldPanel( [InlinePanel("people")], heading="People", help_text="Optional list of people associated with this topic as experts", ), ] # Card panels card_panels = [ FieldPanel("card_title"), FieldPanel("card_description"), ImageChooserPanel("card_image"), ] # Meta panels meta_panels = [ MultiFieldPanel( [ InlinePanel("parent_topics", label="Parent topic(s)"), InlinePanel("child_topics", label="Child topic(s)"), ], heading="Parent/child topic(s)", classname="collapsible collapsed", help_text=( "Topics with no parent (i.e. top-level topics) will be " "listed on the home page. Child topics are listed " "on the parent topic’s page." ), ), MultiFieldPanel( [FieldPanel("icon"), FieldPanel("color")], heading="Theme", help_text=( "Theme settings used on topic page and any tagged content. " "For example, an article tagged with this topic " "will use the color specified here as its accent color." ), ), MultiFieldPanel( [ FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("social_image"), FieldPanel("keywords"), ], heading="SEO", help_text=( "Optional fields to override the default " "title and description for SEO purposes" ), ), ] # Settings panels settings_panels = [FieldPanel("slug"), FieldPanel("show_in_menus")] # Tabs edit_handler = TabbedInterface( [ ObjectList(content_panels, heading="Content"), ObjectList(card_panels, heading="Card"), ObjectList(meta_panels, heading="Meta"), ObjectList(settings_panels, heading="Settings", classname="settings"), ] ) @property def articles(self): return get_combined_articles(self, topics__topic__pk=self.pk) @property def events(self): """Return upcoming events for this topic, ignoring events in the past, ordered by start date""" return get_combined_events( self, topics__topic__pk=self.pk, start_date__gte=datetime.datetime.now() ) @property def videos(self): """Return the latest videos and external videos for this topic. """ return get_combined_videos(self, topics__topic__pk=self.pk) @property def color_value(self): return dict(COLOR_VALUES)[self.color] @property def subtopics(self): return [topic.child for topic in self.child_topics.all()]
class File(Model): attachment = FileField(blank=True,null=True, upload_to='attachment') def __str__(self): return self.attachment.name
class ScorePage(RoutablePageMixin, Page): cover_image = ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=PROTECT, related_name='cover_image') description = StreamField([('rich_text', RichTextBlock()), ('image', ImageChooserBlock())]) duration = DurationField(null=True, blank=True, help_text='Expects data in the format "HH:MM:SS"') file = FileField( upload_to='scores/', validators=[FileExtensionValidator(allowed_extensions=['pdf', 'zip'])]) preview_score = FileField( upload_to='preview_scores/', validators=[FileExtensionValidator(allowed_extensions=['pdf'])]) preview_score_checksum = CharField(editable=False, max_length=256, blank=True) preview_score_checked = False preview_score_updated = False genre = ParentalManyToManyField(Genre, blank=True, related_name='scores') date = CharField(max_length=256, blank=True) instrumentation = ParentalManyToManyField( 'Instrument', blank=True, help_text='The instrumentation of the compostition.') price = DecimalField(max_digits=6, decimal_places=2) materials = RichTextField( blank=True, features=['bold', 'italic', 'link', 'document-link'], help_text='The materials sent in the PDF file.') def save(self, *args, **kwargs): if self.preview_score_checked: # This was the cause of a subtle bug. Because this method can be # called multiple times during model creation, leaving this flag # set would cause the post save hook to fire multiple times. self.preview_score_updated = False return super().save(*args, **kwargs) h = hashlib.md5() for chunk in iter(lambda: self.preview_score.read(8192), b''): h.update(chunk) self.preview_score.seek(0) checksum = h.hexdigest() if not self.preview_score_checksum == checksum: self.preview_score_checksum = checksum self.preview_score_updated = True self.preview_score_checked = True return super().save(*args, **kwargs) @route(r'^([\w-]+)/$') def get_score_file(self, request, score_link_slug): if request.method == 'GET': item_link = get_object_or_404(OrderItemLink, slug=score_link_slug) if item_link.is_expired(): raise Http404() item_link.access_ip = request.META.get('REMOTE_ADDR', '0.0.0.0') item_link.save() return render(request, "main/score_page_download.html", { 'self': self, 'page': self, }) else: raise Http404() @route(r'^$') def score(self, request): cart_page = ShoppingCartPage.objects.first() if request.method == 'POST': in_cart = toggle_score_in_cart(request, self.pk) return render( request, "main/score_page.html", { 'self': self, 'page': self, 'in_cart': in_cart, 'cart_page': cart_page }) else: return render( request, "main/score_page.html", { 'self': self, 'page': self, 'in_cart': score_in_cart(request, self.pk), 'cart_page': cart_page }) class Meta: verbose_name = "Score Page" content_panels = Page.content_panels + [ FieldPanel('date'), FieldPanel('duration'), FieldPanel('genre'), FieldPanel('instrumentation'), FieldPanel('price'), StreamFieldPanel('description'), FieldPanel('materials'), FieldPanel('file'), FieldPanel('preview_score'), ImageChooserPanel('cover_image') ]
class User(AbstractUser): username = CharField(_('username'), max_length=150, primary_key=True) cpf = CharField(_('cpf'), max_length=255, null=True, blank=True) is_active = BooleanField(_('is active'), default=True) is_staff = BooleanField(_('staff status'), default=False) is_superuser = BooleanField(_('superuser status'), default=False) active = CharField(_('active'), max_length=255, null=True, blank=True) presentation_name = CharField(_('presentation name'), max_length=255, null=True, blank=True) civil_name = CharField(_('civil name'), max_length=255, null=True, blank=True) social_name = CharField(_('social name'), max_length=255, null=True, blank=True) first_name = CharField(_('first name'), max_length=255, null=True, blank=True) last_name = CharField(_('last name'), max_length=255, null=True, blank=True) campus = CharField(_('campus'), max_length=255, null=True, blank=True) campus_code = CharField(_('campus code'), max_length=255, null=True, blank=True) department = CharField(_('department'), max_length=255, null=True, blank=True) title = CharField(_('title'), max_length=255, null=True, blank=True) carrer = CharField(_('carrer'), max_length=255, null=True, blank=True) job = CharField(_('job'), max_length=255, null=True, blank=True) polo = CharField(_('polo'), max_length=255, null=True, blank=True, default=_('unknown')) polo_code = CharField(_('polo code'), max_length=255, null=True, blank=True) course = CharField(_('course'), max_length=255, null=True, blank=True) course_code = CharField(_('course code'), max_length=255, null=True, blank=True) email = CharField(_('personal mail'), max_length=250, null=True, blank=True) enterprise_email = CharField(_('enterprise email'), max_length=250, null=True, blank=True) academic_email = CharField(_('academic email'), max_length=250, null=True, blank=True) scholar_email = CharField(_('scholar email'), max_length=250, null=True, blank=True) eduroam = CharField(_('eduroam'), max_length=250, null=True, blank=True) first_access = DateTimeField(_('date joined'), auto_now_add=True) last_access = DateTimeField(_('last access'), auto_now=True) deleted = DateTimeField(_('deleted at'), null=True, blank=True) created_at = DateTimeField(_('created at'), null=True, blank=True) changed_at = DateTimeField(_('changed at'), null=True, blank=True) password_set_at = DateTimeField(_('password set at'), null=True, blank=True) last_access_at = DateTimeField(_('last ad access'), null=True, blank=True) member_of = ArrayField(CharField(_('member_of'), max_length=1000), null=True, blank=True) photo_url = CharField(_('photo'), max_length=250, null=True, blank=True) biografy = TextField(_('biografy'), blank=True, null=True) is_biografy_public = TextField(_('show to all'), blank=True, null=True) valid_photo = FileField(_('valid photo'), null=True, blank=True) pending_photo = FileField(_('pending photo'), null=True, blank=True) photo_solicitation_at = DateTimeField(_('photo_solicitation_at'), blank=True, null=True) photo_approved_at = DateTimeField(_('photo_approved at'), blank=True, null=True) photo_approved_by = CharField(_('photo_approved by'), max_length=250, blank=True, null=True) font_size = SmallIntegerField(_('font size'), blank=True, null=True) theme_skin = CharField(_('theme skin'), choices=suap_ead.skins, max_length=250, blank=True, null=True) legends = NullBooleanField(_('legends'), blank=True, null=True) sign_language = NullBooleanField(_('sign language'), blank=True, null=True) screen_reader = NullBooleanField(_('screen reader'), blank=True, null=True) special_needs = ManyToManyField(SpecialNeed, verbose_name=_('special needs'), blank=True) is_special_needs_public = NullBooleanField(_('show to all')) REQUIRED_FIELDS = [] class Meta: verbose_name = _('User') verbose_name_plural = _('Users') ordering = ['first_name'] def __str__(self): return self.printing_name def save(self, *args, **kwargs): self.is_active = settings.LDAP_ACTIVE_VALUE == self.active if settings.USE_LDAP else True self.created_at = _cast_timestamp(self.created_at) self.changed_at = _cast_timestamp(self.changed_at) self.password_set_at = _cast_timestamp(self.password_set_at) self.last_access_at = _cast_timestamp(self.last_access_at) self.email = self.email or self.enterprise_email or self.academic_email or self.scholar_email self.civil_name = "%s %s" % (self.first_name, self.last_name) self.presentation_name = self.social_name or self.civil_name super().save(*args, **kwargs) @property def printing_name(self): if self.social_name and self.social_name != self.civil_name: return "%s (%s)" % (self.social_name, self.civil_name) return self.civil_name @property def sigla(self): parts = self.presentation_name.split(" ") if len(parts) > 1: return ("%s%s" % (parts[0][0], parts[-1][0])).upper() else: return parts[0][0].upper() @property def status(self): result = "" result += "%s " % (_("active") if self.is_active else _("inactive")) if self.is_superuser: result += "(%s" % _("superuser") if not self.is_staff: result += " %s" % _("but not a staff") result += ")" elif self.is_staff: result += "(%s)" % _("staff") else: result += "(%s)" % _("user") return result
class AudioVideoAbstract(Source): fichier_ogg = FileField( _('fichier (OGG)'), upload_to='files/ogg/', blank=True, editable=False, ) fichier_mpeg = FileField( _('fichier (MPEG)'), upload_to='files/mpeg/', blank=True, editable=False, ) extrait = FileField(_('extrait'), upload_to='files/extraits/', blank=True) extrait_ogg = FileField( _('extrait (OGG)'), upload_to='files/extraits/ogg/', blank=True, editable=False, ) extrait_mpeg = FileField( _('extrait (MPEG)'), upload_to='files/extraits/mpeg/', blank=True, editable=False, ) format = CharField(_('format'), max_length=10, blank=True, editable=False) format_extrait = CharField( _('format de l’extrait'), max_length=10, blank=True, editable=False, ) duree = PositiveSmallIntegerField( _('durée (en secondes)'), null=True, blank=True, editable=False, ) duree_extrait = PositiveSmallIntegerField( _('durée de l’extrait (en secondes)'), null=True, blank=True, editable=False, ) type_fichier_attendu = None format_ogg_attendu = 'ogg' format_mpeg_attendu = 'mpeg' class Meta: abstract = True def update_media_info(self): if ( (self.fichier_ogg and self.fichier_mpeg) and (self.extrait_ogg and self.extrait_mpeg) ): return elif not self.fichier and not self.extrait: message = _( 'Vous devez remplir au moins « fichier » ou « extrait ».' ) raise ValidationError({'fichier': message, 'extrait': message}) if self.fichier: file_analyzer = FileAnalyzer(self, 'fichier') file_analyzer.validate(expected_type=self.type_fichier_attendu) self.format = file_analyzer.format_name self.duree = file_analyzer.avprobe_info.duration if isinstance(self, Video): self.largeur = file_analyzer.avprobe_info.width self.hauteur = file_analyzer.avprobe_info.height if self.fichier_ogg: file_analyzer = FileAnalyzer(self, 'fichier_ogg') file_analyzer.validate( expected_type=self.type_fichier_attendu, expected_format=self.format_ogg_attendu, ) if self.fichier_mpeg: file_analyzer = FileAnalyzer(self, 'fichier_mpeg') file_analyzer.validate( expected_type=self.type_fichier_attendu, expected_format=self.format_mpeg_attendu, ) if self.extrait: file_analyzer = FileAnalyzer(self, 'extrait') file_analyzer.validate(expected_type=self.type_fichier_attendu) self.format_extrait = file_analyzer.format_name self.duree_extrait = file_analyzer.avprobe_info.duration if isinstance(self, Video): self.largeur_extrait = file_analyzer.avprobe_info.width self.hauteur_extrait = file_analyzer.avprobe_info.height if self.extrait_ogg: file_analyzer = FileAnalyzer(self, 'extrait_ogg') file_analyzer.validate( expected_type=self.type_fichier_attendu, expected_format=self.format_ogg_attendu, ) if self.extrait_mpeg: file_analyzer = FileAnalyzer(self, 'extrait_mpeg') file_analyzer.validate( expected_type=self.type_fichier_attendu, expected_format=self.format_mpeg_attendu, ) self.type_fichier = self.type_fichier_attendu
class Request(models.Model): id = CharField(max_length=36, default=uuid4, primary_key=True) path = CharField(max_length=190, db_index=True) query_params = TextField(blank=True, default='') raw_body = TextField(blank=True, default='') body = TextField(blank=True, default='') method = CharField(max_length=10) start_time = DateTimeField(default=timezone.now, db_index=True) view_name = CharField(max_length=190, db_index=True, blank=True, default='', null=True) end_time = DateTimeField(null=True, blank=True) time_taken = FloatField(blank=True, null=True) encoded_headers = TextField(blank=True, default='') # stores json meta_time = FloatField(null=True, blank=True) meta_num_queries = IntegerField(null=True, blank=True) meta_time_spent_queries = FloatField(null=True, blank=True) pyprofile = TextField(blank=True, default='') prof_file = FileField(max_length=300, blank=True, storage=silk_storage, null=True) # Useful method to create shortened copies of strings without losing start and end context # Used to ensure path and view_name don't exceed 190 characters def _shorten(self, string): return '%s...%s' % (string[:94], string[len(string) - 93:]) @property def total_meta_time(self): return (self.meta_time or 0) + (self.meta_time_spent_queries or 0) @property def profile_table(self): for n, columns in enumerate(parse_profile(self.pyprofile)): location = columns[-1] if n and '{' not in location and '<' not in location: r = re.compile('(?P<src>.*\.py)\:(?P<num>[0-9]+).*') m = r.search(location) group = m.groupdict() src = group['src'] num = group['num'] name = 'c%d' % n fmt = '<a name={name} href="?pos={n}&file_path={src}&line_num={num}#{name}">{location}</a>' rep = fmt.format(**dict(group, **locals())) yield columns[:-1] + [mark_safe(rep)] else: yield columns # defined in atomic transaction within SQLQuery save()/delete() as well # as in bulk_create of SQLQueryManager # TODO: This is probably a bad way to do this, .count() will prob do? num_sql_queries = IntegerField(default=0) # TODO replace with count() @property def time_spent_on_sql_queries(self): """ TODO: Perhaps there is a nicer way to do this with Django aggregates? My initial thought was to perform: SQLQuery.objects.filter.aggregate(Sum(F('end_time')) - Sum(F('start_time'))) However this feature isnt available yet, however there has been talk for use of F objects within aggregates for four years here: https://code.djangoproject.com/ticket/14030. It looks like this will go in soon at which point this should be changed. """ return sum(x.time_taken for x in SQLQuery.objects.filter(request=self)) @property def headers(self): if self.encoded_headers: raw = json.loads(self.encoded_headers) else: raw = {} return CaseInsensitiveDictionary(raw) @property def content_type(self): return self.headers.get('content-type', None) @classmethod def garbage_collect(cls, force=False): """ Remove Request/Responses when we are at the SILKY_MAX_RECORDED_REQUESTS limit Note that multiple in-flight requests may call this at once causing a double collection """ check_percent = SilkyConfig().SILKY_MAX_RECORDED_REQUESTS_CHECK_PERCENT check_percent /= 100.0 if check_percent < random.random() and not force: return target_count = SilkyConfig().SILKY_MAX_RECORDED_REQUESTS # Since garbage collection is probabilistic, the target count should # be lowered to account for requests before the next garbage collection if check_percent != 0: target_count -= int(1 / check_percent) # Make sure we can delete everything if needed by settings if target_count <= 0: cls.objects.all().delete() return requests = cls.objects.order_by('-start_time') if not requests or len(requests) - 1 < target_count: return time_cutoff = requests[target_count].start_time cls.objects.filter(start_time__lte=time_cutoff).delete() def save(self, *args, **kwargs): # sometimes django requests return the body as 'None' if self.raw_body is None: self.raw_body = '' if self.body is None: self.body = '' if self.end_time and self.start_time: interval = self.end_time - self.start_time self.time_taken = interval.total_seconds() * 1000 # We can't save if either path or view_name exceed 190 characters if self.path and len(self.path) > 190: self.path = self._shorten(self.path) if self.view_name and len(self.view_name) > 190: self.view_name = self._shorten(self.view_name) super(Request, self).save(*args, **kwargs) Request.garbage_collect(force=False)
def test_callable_class_storage_file_field(self): class GetStorage(FileSystemStorage): pass obj = FileField(storage=GetStorage) self.assertIsInstance(obj.storage, BaseStorage)
class ContentMedia(Model): data = FileField( blank=False, editable=True, upload_to="content_media", max_length=256) def __unicode__(self): return basename(self.data.url)
class Person(BaseModelMixin): first_name = CharField('Имя', max_length=50) last_name = CharField('Фамилия', max_length=50) middle_name = CharField('Отчество', max_length=50, default='', blank=True) birth_date = DateField('Дата рождения', null=True, blank=True) user = OneToOneField(User, verbose_name='Пользователь', on_delete=PROTECT, related_name='person', null=True, blank=True) passport_number = CharField('Паспорт', max_length=10, null=True, blank=True, unique=True) passport_date = DateField('Дата выдачи паспорта', null=True, blank=True) phone = CharField('Телефон', max_length=15, null=True, blank=True) WORKED = 'worked' FIRED = 'fired' CANDIDATE = 'candidate' REFUSED = 'refused' APPROVED = 'approved' RESERVE = 'reserve' STATUSES = ( (WORKED, 'Работает'), (FIRED, 'Уволен'), (CANDIDATE, 'Кандидат'), (REFUSED, 'Отклонен'), (APPROVED, 'Проверен'), (RESERVE, 'В резерве'), ) status = CharField('Статус', choices=STATUSES, max_length=10, null=True, blank=True) station = ForeignKey('Station', verbose_name='Станция', on_delete=PROTECT, related_name='persons', null=True, blank=True) def save_to_path(self, filename): valid_extensions = [ 'pdf', ] ext = filename.rsplit('.', 1)[1] if ext.lower() in valid_extensions: raise ValidationError('Unsupported file extension.') return f'documents/{self.passport_number.lower()}.{ext}' document_file = FileField(upload_to=save_to_path, verbose_name='Файл документа', null=True, blank=True) @property def full_name(self): return f'{self.first_name} {self.last_name} {self.middle_name}'.strip() @property def short_name(self): return f'{self.first_name} {self.last_name[0]}.' def __str__(self): return f"{self.full_name} ({self.passport_number})" class Meta: verbose_name = _('Персона') verbose_name_plural = _('Персоны')
def test_file_field_storage_none_uses_default_storage(self): self.assertEqual(FileField().storage, default_storage)
class Youtube(models.Model): title = models.TextField(null=False, blank=False, default="") content = models.TextField(null=False, blank=False, default="") video = FileField(upload_to='Youtube') thumbnail = ImageField(upload_to="Youtube_thumnail")
class Stylesheet(FileResource): source = FileField(upload_to='stylesheets')
class Script(FileResource): source = FileField(upload_to='scripts')
def test_filefield_dangerous_filename_dir(self): f = FileField(upload_to='some/folder/') msg = "File name '/tmp/path' includes path elements" with self.assertRaisesMessage(SuspiciousFileOperation, msg): f.generate_filename(None, '/tmp/path')
class ContentPage(BasePage): parent_page_types = ["home.HomePage", "content.ContentPage", "topics.Topic"] subpage_types = ["people.People", "content.ContentPage"] template = "content.html" # Content fields description = RichTextField( blank=True, default="", features=RICH_TEXT_FEATURES_SIMPLE, help_text=( "Optional short text description, " f"max. {DESCRIPTION_MAX_LENGTH} characters" ), max_length=DESCRIPTION_MAX_LENGTH, ) body = CustomStreamField( help_text=( "Main page body content. Supports rich text, images, embed via URL, " "embed via HTML, and inline code snippets" ) ) sidebar = CustomStreamField( null=True, blank=True, help_text=( "Sidebar page body content (narrower than main body). Rendered to the " "right of the main body content in desktop and below it in mobile." "Supports rich text, images, embed via URL, " "embed via HTML, and inline code snippets" ), ) # Card fields card_title = CharField("Title", max_length=140, blank=True, default="") card_description = TextField("Description", max_length=140, blank=True, default="") card_image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", verbose_name="Image", help_text="An image in 16:9 aspect ratio", ) card_image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", verbose_name="Image", help_text="An image in 16:9 aspect ratio", ) card_image_3_2 = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", verbose_name="Image", help_text="An image in 3:2 aspect ratio", ) # Meta fields nav_description = TextField( "Navigation description", max_length=DESCRIPTION_MAX_LENGTH, blank=True, default="", ) icon = FileField( upload_to="contentpage/icons", blank=True, default="", help_text=( "MUST be a black-on-transparent SVG icon ONLY, " "with no bitmap embedded in it." ), validators=[check_for_svg_file], ) keywords = ClusterTaggableManager(through=ContentPageTag, blank=True) # Editor panel configuration content_panels = BasePage.content_panels + [ FieldPanel("description"), StreamFieldPanel("body"), StreamFieldPanel("sidebar"), ] # Card panels card_panels = [ FieldPanel( "card_title", help_text=( "Title displayed when this page is " "represented by a card in a list of items. " "If blank, the page's title is used." ), ), FieldPanel( "card_description", help_text=( "Summary text displayed when this page is " "represented by a card in a list of items. " "If blank, the page's description is used." ), ), MultiFieldPanel( [ImageChooserPanel("card_image")], heading="16:9 Image", help_text=( "Image used for representing this page as a Card. " "Should be 16:9 aspect ratio. " "If not specified a fallback will be used. " "This image is also shown when sharing this page via social " "media unless a social image is specified." ), ), MultiFieldPanel( [ImageChooserPanel("card_image_3_2")], heading="3:2 Image", help_text=( "Image used for representing this page as a Card. " "Should be 3:2 aspect ratio. " "If not specified a fallback will be used. " ), ), ] # Meta panels meta_panels = [ FieldPanel( "nav_description", help_text="Text to display in the navigation with the title for this page.", ), MultiFieldPanel( [FieldPanel("icon")], heading="Theme", help_text=( "This icon will be used if, for example, this page is shown in a Menu" ), ), MultiFieldPanel( [ FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("social_image"), FieldPanel("keywords"), ], heading="SEO", help_text=( "Optional fields to override the default title and " "description for SEO purposes" ), ), ] # Settings panels settings_panels = BasePage.settings_panels + [ FieldPanel("slug"), FieldPanel("show_in_menus"), ] # Tabs edit_handler = TabbedInterface( [ ObjectList(content_panels, heading="Content"), ObjectList(card_panels, heading="Card"), ObjectList(meta_panels, heading="Meta"), ObjectList(settings_panels, heading="Settings", classname="settings"), ] ) # Search config search_fields = BasePage.search_fields + [ # Inherit search_fields from Page # "title" is already specced in BasePage index.SearchField("description"), index.SearchField("body"), index.SearchField("sidebar"), # Add FilterFields for things we may be filtering on (eg topics) index.FilterField("slug"), ]
class Source(AutoriteModel): parent = ForeignKey( 'self', related_name='children', verbose_name=_('parent'), null=True, blank=True, on_delete=CASCADE, help_text=_( 'À remplir par exemple si la source est une page d’un recueil ' 'déjà existant ou un tome d’une série.' ), ) position = PositiveIntegerField( _('position'), null=True, blank=True, help_text=_('Position au sein de son parent.') ) est_promu = BooleanField(_('est dans la bibliothèque'), default=False) type = ForeignKey('TypeDeSource', related_name='sources', help_text=ex(_('compte rendu')), verbose_name=_('type'), on_delete=PROTECT) titre = CharField(_('titre'), max_length=200, blank=True, db_index=True, help_text=ex(_('Journal de Rouen'))) legende = CharField(_('légende'), max_length=600, blank=True, help_text=_('Recommandée pour les images.')) ancrage = AncrageSpatioTemporel(has_heure=False, has_lieu=False) numero = NumberCharField( _('numéro'), max_length=50, blank=True, db_index=True, help_text=_('Sans « № ». Exemple : « 52 »'), ) folio = CharField(_('folio'), max_length=15, blank=True, help_text=_('Sans « f. ». Exemple : « 3 ».')) page = CharField(_('page'), max_length=15, blank=True, db_index=True, help_text=_('Sans « p. ». Exemple : « 3 »')) lieu_conservation = CharField(_('lieu de conservation'), max_length=75, blank=True, db_index=True) cote = CharField(_('cote'), max_length=60, blank=True, db_index=True) url = URLField(_('URL'), blank=True, help_text=_('Uniquement un permalien extérieur à Dezède.')) transcription = HTMLField( _('transcription'), blank=True, help_text=_('Recopier la source ou un extrait en suivant les règles ' 'définies dans ' # FIXME: Don’t hardcode the URL. '<a href="/examens/source">le didacticiel.</a>'), ) fichier = FileField(_('fichier'), upload_to='files/', blank=True) TYPES = ( (FileAnalyzer.OTHER, _('autre')), (FileAnalyzer.IMAGE, _('image')), (FileAnalyzer.AUDIO, _('audio')), (FileAnalyzer.VIDEO, _('vidéo')), ) type_fichier = PositiveSmallIntegerField( choices=TYPES, null=True, blank=True, editable=False, db_index=True, ) telechargement_autorise = BooleanField( _('téléchargement autorisé'), default=True, ) evenements = ManyToManyField( 'Evenement', through='SourceEvenement', related_name='sources', verbose_name=_('événements')) oeuvres = ManyToManyField('Oeuvre', through='SourceOeuvre', related_name='sources', verbose_name=_('œuvres')) individus = ManyToManyField( 'Individu', through='SourceIndividu', related_name='sources', verbose_name=_('individus')) ensembles = ManyToManyField( 'Ensemble', through='SourceEnsemble', related_name='sources', verbose_name=_('ensembles')) lieux = ManyToManyField('Lieu', through='SourceLieu', related_name='sources', verbose_name=_('lieux')) parties = ManyToManyField( 'Partie', through='SourcePartie', related_name='sources', verbose_name=_('sources')) # # Dossier # # Métadonnées editeurs_scientifiques = ManyToManyField( 'accounts.HierarchicUser', related_name='sources_editees', verbose_name=_('éditeurs scientifiques'), blank=True, ) date_publication = DateField(_('date de publication'), default=datetime.datetime.now) publications = TextField(_('publication(s) associée(s)'), blank=True) developpements = TextField(_('développements envisagés'), blank=True) # Article presentation = TextField(_('présentation'), blank=True) contexte = TextField(_('contexte historique'), blank=True) sources_et_protocole = TextField(_('sources et protocole'), blank=True) bibliographie = TextField(_('bibliographie indicative'), blank=True) objects = SourceManager() class Meta: verbose_name = _('source') verbose_name_plural = _('sources') ordering = ( 'date', 'titre', 'numero', 'parent__date', 'parent__titre', 'parent__numero', 'position', 'page', 'lieu_conservation', 'cote', ) permissions = (('can_change_status', _('Peut changer l’état')),) def __str__(self): return strip_tags(self.html(False)) def has_presentation_tab(self): def iterator(): yield self.editeurs_scientifiques.exists() yield self.publications yield self.developpements yield self.presentation yield self.contexte yield self.sources_et_protocole yield self.bibliographie return any(iterator()) def has_index_tab(self): def iterator(): yield self.parent yield self.auteurs_html() yield self.nested_individus() yield self.nested_oeuvres() yield self.nested_parties() yield self.nested_evenements() yield self.nested_ensembles() yield self.notes_publiques return any(iterator()) @cached_property def specific(self): if self.type_fichier == FileAnalyzer.AUDIO: return Audio.objects.get(pk=self.pk) if self.type_fichier == FileAnalyzer.VIDEO: return Video.objects.get(pk=self.pk) return self @permalink def get_absolute_url(self): return 'source_permanent_detail', (self.pk,) @permalink def get_change_url(self): meta = self.specific._meta return f'admin:{meta.app_label}_{meta.model_name}_change', (self.pk,) def permalien(self): return self.get_absolute_url() def link(self): return self.html() link.short_description = _('Lien') link.allow_tags = True def auteurs_html(self, tags=True): return self.auteurs.html(tags) def no(self): return ugettext('n° %s') % self.numero def f(self): return ugettext('f. %s') % self.folio def p(self): return ugettext('p. %s') % self.page def html(self, tags=True, pretty_title=False, link=True): url = None if not tags else self.get_absolute_url() conservation = hlp(self.lieu_conservation, ugettext('Lieu de conservation'), tags) if self.ancrage.date or self.ancrage.date_approx: ancrage = hlp(self.ancrage.html(tags, caps=False), ugettext('date')) else: ancrage = None if self.cote: conservation += f", {hlp(self.cote, 'cote', tags)}" if self.titre: l = [cite(self.titre, tags)] if self.numero: l.append(self.no()) if ancrage is not None: l.append(ancrage) if self.lieu_conservation: l[-1] += f' ({conservation})' else: l = [conservation] if ancrage is not None: l.append(ancrage) if self.folio: l.append(hlp(self.f(), ugettext('folio'), tags)) if self.page: l.append(hlp(self.p(), ugettext('page'), tags)) if self.parent is not None: l.insert( 0, self.parent.html(tags=tags, pretty_title=pretty_title, link=pretty_title) ) l = (l[0], small(str_list(l[1:]), tags=tags)) if pretty_title else l out = str_list(l) if link: return mark_safe(href(url, out, tags)) return out html.short_description = _('rendu HTML') html.allow_tags = True def pretty_title(self): return self.html(pretty_title=True, link=False) def has_events(self): if hasattr(self, '_has_events'): return self._has_events return self.evenements.exists() has_events.short_description = _('événements') has_events.boolean = True has_events.admin_order_field = 'evenements' def has_program(self): if hasattr(self, '_has_program'): return self._has_program return self.evenements.with_program().exists() has_program.short_description = _('programme') has_program.boolean = True def is_other(self): return self.type_fichier == FileAnalyzer.OTHER def is_pdf(self): return self.is_other() and self.fichier.name.endswith('.pdf') def is_image(self): return self.type_fichier == FileAnalyzer.IMAGE def is_audio(self): return self.type_fichier == FileAnalyzer.AUDIO def is_video(self): return self.type_fichier == FileAnalyzer.VIDEO def has_children_images(self): return self.children.filter(type_fichier=FileAnalyzer.IMAGE).exists() def has_images(self): return ( self.type_fichier == FileAnalyzer.IMAGE or self.has_children_images() ) def has_fichiers(self): return ( self.is_other() or self.is_audio() or self.is_video() or self.has_images() ) def images_iterator(self): if self.is_image(): yield self for child in Source.objects.filter( Q(parent=self) | Q(parent__parent=self), type_fichier=FileAnalyzer.IMAGE, ).order_by('position', 'page'): yield child @cached_property def images(self): return list(self.images_iterator()) @cached_property def preview_image(self): return next(self.images_iterator()) @cached_property def is_collection(self): return Source.objects.filter(parent__parent=self).exists() def is_empty(self): return not (self.transcription or self.url or self.has_fichiers()) DATA_TYPES = ('video', 'audio', 'image', 'other', 'text', 'link') VIDEO, AUDIO, IMAGE, OTHER, TEXT, LINK = DATA_TYPES @property def data_types(self): data_types = [] if self.is_video(): data_types.append(self.VIDEO) if self.is_audio(): data_types.append(self.AUDIO) if self.has_images(): data_types.append(self.IMAGE) if self.is_other(): data_types.append(self.OTHER) if self.transcription: data_types.append(self.TEXT) if self.url: data_types.append(self.LINK) return data_types ICONS = { VIDEO: '<i class="fa fa-fw fa-video-camera"></i>', AUDIO: '<i class="fa fa-fw fa-volume-up"></i>', IMAGE: '<i class="fa fa-fw fa-photo"></i>', OTHER: '<i class="fa fa-fw fa-paperclip"></i>', TEXT: '<i class="fa fa-fw fa-file-text-o"></i>', LINK: '<i class="fa fa-fw fa-external-link"></i>', } DATA_TYPES_WITH_ICONS = ( (VIDEO, _(f'{ICONS[VIDEO]} Vidéo')), (AUDIO, _(f'{ICONS[AUDIO]} Audio')), (IMAGE, _(f'{ICONS[IMAGE]} Image')), (OTHER, _(f'{ICONS[OTHER]} Autre')), (TEXT, _(f'{ICONS[TEXT]} Texte')), (LINK, _(f'{ICONS[LINK]} Lien')), ) @property def icons(self): return ''.join([self.ICONS[data_type] for data_type in self.data_types]) def update_media_info(self): if self.fichier: file_analyzer = FileAnalyzer(self, 'fichier') self.type_fichier = file_analyzer.type else: self.type_fichier = None def clean(self): super().clean() if not getattr(self, 'updated_media_info', False): self.update_media_info() self.updated_media_info = True @cached_property def first_page(self): return self.children.order_by('position').first() @cached_property def prev_page(self): if self.parent is not None: return self.parent.children.exclude(pk=self.pk).filter( position__lte=self.position, ).order_by('-position').first() @cached_property def next_page(self): if self.parent is not None: return self.parent.children.exclude(pk=self.pk).filter( position__gte=self.position, ).order_by('position').first() @property def filename(self): return Path(self.fichier.path).name @cached_property def linked_individus(self): return self.individus.distinct() @cached_property def linked_evenements(self): return self.evenements.distinct() @cached_property def linked_oeuvres(self): return self.oeuvres.distinct() @cached_property def linked_ensembles(self): return self.ensembles.distinct() @cached_property def linked_lieux(self): return self.lieux.distinct() @cached_property def linked_parties(self): return self.parties.distinct() def get_linked_objects(self): return [ *self.auteurs.all(), *self.linked_individus, *self.linked_evenements, *self.linked_oeuvres, *self.linked_ensembles, *self.linked_lieux, *self.linked_parties, ] def get_linked_objects_json(self): return json.dumps([ { 'url': obj.get_absolute_url(), 'label': str(obj), 'model': obj.class_name().lower(), } for obj in self.get_linked_objects() ]) def nested_evenements(self): return apps.get_model('libretto.Evenement').objects.filter( sources__in=self.children.all() | Source.objects.filter(pk=self.pk) ).distinct() def nested_oeuvres(self): return apps.get_model('libretto.Oeuvre').objects.filter( sources__in=self.children.all() | Source.objects.filter(pk=self.pk) ).distinct() def nested_individus(self): return apps.get_model('libretto.Individu').objects.filter( sources__in=self.children.all() | Source.objects.filter(pk=self.pk) ).distinct() def nested_ensembles(self): return apps.get_model('libretto.Ensemble').objects.filter( sources__in=self.children.all() | Source.objects.filter(pk=self.pk) ).distinct() def nested_lieux(self): return apps.get_model('libretto.Lieu').objects.filter( sources__in=self.children.all() | Source.objects.filter(pk=self.pk) ).distinct() def nested_parties(self): return apps.get_model('libretto.Partie').objects.filter( sources__in=self.children.all() | Source.objects.filter(pk=self.pk) ).distinct() @property def small_thumbnail(self): if self.is_image(): thumbnailer = get_thumbnailer(self.fichier) return thumbnailer.get_thumbnail(aliases.get('small')).url @property def medium_thumbnail(self): if self.is_image(): thumbnailer = get_thumbnailer(self.fichier) return thumbnailer.get_thumbnail(aliases.get('medium')).url @staticmethod def autocomplete_search_fields(): return ( 'type__nom__unaccent__icontains', 'titre__unaccent__icontains', 'date__icontains', 'date_approx__unaccent__icontains', 'numero__unaccent__icontains', 'lieu_conservation__unaccent__icontains', 'cote__unaccent__icontains', )
class FileFlag(BaseFlag): """ A more advanced flag which allows for file submission """ file = FileField() check_function = CharField( max_length=255, help_text="Name of function that will be called to check the file")
class TemporaryStorage(DjangoModel): # This is the storage and reading of the initial cellml file file = FileField(blank=False) tree = TextField(blank=True) owner = ForeignKey(Person, related_name="stored_files", on_delete=CASCADE, null=True, blank=True)
class CreativeVideo(Creative): ''' Creative video data model. Extends basic model with field to store video files (uploaded to LiveRail). ''' file = FileField(upload_to='creatives') allowed_extensions = ('api') # Fields connected to LiveRail integration. liverail_id = IntegerField(max_length=20, null=True, db_index=True) liverail_status = CharField(max_length=20, choices=LIVERAIL_STATUS_CHOICES, default=LiverailStatus.pending, db_index=True) liverail_error = TextField(blank=True, default='') media_variants = JSONField(blank=True) # Duration of content, stored in seconds. duration = IntegerField(null=True, default=0) @property def media_variants_secure(self): """Provide a list of media_variants that can be served over https.""" secure_variants = [] for media in self.media_variants: secure_media = media.copy() secure_media['url'] = secure_media['url'].replace( LiveRailAPI.LIVERAIL_CDN, LiveRailAPI.LIVERAIL_CDN_SECURE) secure_variants.append(secure_media) return secure_variants @property def file_path_abs(self): return settings.MEDIA_ROOT / self.file.name @property def is_auditable(self): is_auditable = super(CreativeVideo, self).is_auditable return is_auditable and self.liverail_status == LiverailStatus.active @property def appnexus_media_url(self): return appnexus_vast_url( self.public_id, # is_secure set to False will create a vast url with http:// scheme, which is required by appnexus. is_secure=False) @property def vast_url(self): """ Return vast url without any url schema. :returns: schema-less url to vast :rtype: str """ return appnexus_vast_url( self.public_id, # is_secure None will render url without any scheme, leaving it out to browser. is_secure=None, ) @property def appnexus_template_id(self): if self.duration == 15: return AppCreativeTemplate.video_vast_15s if self.duration == 30: return AppCreativeTemplate.video_vast_30s return AppCreativeTemplate.video_vast_other @property def api_data(self): if self.file: return self.file.url @api_data.setter def api_data(self, videoname): video = '' if videoname: video = videoname.replace(settings.MEDIA_URL, '') if video == self.file: return file_path_abs = settings.MEDIA_ROOT / video if not magic.from_file(file_path_abs, mime=True).startswith('video'): raise ValidationError("Uploaded video file isn't recognizable " "as a video filetype.") self.file = video def upload_to_cdn(self, contents): """ Upload file to liverail cdn. :param str contents: read video file content. """ with liverail.liverail_connection() as api: if not self.liverail_id: self.liverail_id = api.add_creative(self.pk, self.name) api.upload_video_asset(self.liverail_id, contents) self.liverail_status = LiverailStatus.uploaded self.save() def liverail_update_media(self, liverail_conn): """ Update creative's LiveRail status. If the real updated status is 'active', save updated media types and set duration, width and height to their maximum of returned videos. :param apis.liverail.LiveRailAPI liverail_conn: LiveRail API wrapper in connected state """ try: creative_status = liverail_conn.creative_status(self.liverail_id) if creative_status != liverail_conn.CreativeStatus.active: return video_assets = liverail_conn.list_video_assets(self.liverail_id) except liverail.LiveRailApiResponseError as e: self.liverail_error = str(e) self.liverail_status = LiverailStatus.error self.save(update_fields=['liverail_error', 'liverail_status']) return # Update the creative: self.liverail_status = LiverailStatus.active self.media_variants = video_assets self.width = max(v['width'] for v in video_assets) self.height = max(v['height'] for v in video_assets) # Duration should be constant but who knows (different encodings, different framerates, rounding...). self.duration = max(v['duration'] for v in video_assets) self.save(update_fields=[ 'width', 'height', 'duration', 'liverail_status', 'media_variants' ]) @classmethod def liverail_update_media_all(cls): """ Update media files list of all not active creatives with liverail_id. """ with liverail.liverail_connection() as api: for creative in cls.objects_visible.filter( liverail_id__isnull=False).exclude( liverail_status=LiverailStatus.active): creative.liverail_update_media(liverail_conn=api) def upload_to_cdn_async(self): """ Overrides base method. Sets liverail_status to pending when creative is being uploaded to service. """ # We can't execute save, because this method is called during # post_save signal, so we use update to avoid triggering signals at all. CreativeVideo.objects.filter(pk=self.pk).update( liverail_status=LiverailStatus.pending) return super(CreativeVideo, self).upload_to_cdn_async()
class CompositionPage(Page): composition_title = RichTextField(features=['bold', 'italic']) description = StreamField([('rich_text', RichTextBlock()), ('image', ImageChooserBlock())], blank=True) location = RichTextField( blank=True, features=['bold', 'italic', 'link', 'document-link'], ) genre = ParentalManyToManyField(Genre, blank=True, related_name='compositions') instrumentation = ParentalManyToManyField( 'Instrument', blank=True, ) orchestration = RichTextField( blank=True, features=['bold', 'italic'], help_text=( 'If the composition is for an ensemble, use this field to enter ' 'the orchestration of the work.')) duration = DurationField(null=True, blank=True, help_text='Expects data in the format "HH:MM:SS"') dedicatee = RichTextField( blank=True, features=['bold', 'italic', 'link', 'document-link'], ) text_source = RichTextField( blank=True, features=['bold', 'italic', 'link', 'document-link'], help_text='The source of the text used in the compostition.') collaborator = RichTextField( blank=True, features=['bold', 'italic', 'link', 'document-link'], help_text='Others that Decruck collaborated with.') manuscript_status = RichTextField( blank=True, features=['bold', 'italic', 'link', 'document-link'], help_text='Notes about the location and condition of the manuscript.') recording = StreamField([('rich_text', RichTextBlock()), ('image', ImageChooserBlock())], blank=True) information_up_to_date = BooleanField(default=False) scanned = BooleanField(default=False) premiere = RichTextField( blank=True, features=['bold', 'italic', 'link', 'document-link'], ) # For preview score preview_score = FileField( upload_to='composition_preview_scores/', blank=True, null=True, validators=[FileExtensionValidator(allowed_extensions=['pdf'])]) preview_score_checksum = CharField(editable=False, max_length=256, blank=True) preview_score_checked = False preview_score_updated = False # Extended Date Time Format nat_lang_edtf_string = CharField( verbose_name='Natural Language Date', help_text=('The EDTF date in natural language. This field is help ' 'users who aren\'t familiar with the EDTF. It does not ' 'change how the date is represented.'), blank=True, max_length=256) edtf_string = CharField( verbose_name='EDTF Date', help_text=mark_safe( 'A date in the <a href="https://www.loc.gov/standards/datetime/" ' 'target="_blank"><strong>Extended Date Time Format</strong></a>'), blank=True, max_length=256) lower_fuzzy = DateField(editable=False, null=True, blank=True) upper_fuzzy = DateField(editable=False, null=True, blank=True) lower_strict = DateField(editable=False, null=True, blank=True) upper_strict = DateField(editable=False, null=True, blank=True) nat_lang_year = CharField(editable=False, max_length=9, blank=True) def instrumentation_list(self): return ', '.join([str(i) for i in self.instrumentation.all()]) class Meta: verbose_name = "Composition" def get_context(self, request, *args, **kwargs): ctx = super().get_context(request, *args, **kwargs) try: search_idx = request.session['comp_search_index'] if search_idx: idx = search_idx.index(self.pk) prev_url = None next_url = None if idx > 0: pk = search_idx[idx - 1] prev_url = CompositionPage.objects.get(pk=pk).url if idx < len(search_idx) - 1: pk = search_idx[idx + 1] next_url = CompositionPage.objects.get(pk=pk).url ctx['prev_url'] = prev_url ctx['next_url'] = next_url ctx['comp_search_qs'] = request.\ session.get('comp_search_qs', '') except (KeyError, ValueError): pass return ctx def clean(self): super().clean() # Per Django docs: validate and modify values in Model.clean() # https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.clean # Check that nat_lang_edtf_string and edtf_string are either both set, or both unset if (self.nat_lang_edtf_string and not self.edtf_string) or (not self.nat_lang_edtf_string and self.edtf_string): raise ValidationError( 'If setting a date on a composition, an EDTF string and a natural language EDTF string must be provided.' ) # Validate edtf_string if self.edtf_string and self.nat_lang_edtf_string: try: e = parse_edtf(self.edtf_string) except EDTFParseException: raise ValidationError({ 'edtf_string': '{} is not a valid EDTF string'.format(self.edtf_string) }) self.lower_fuzzy = struct_time_to_date(e.lower_fuzzy()) self.upper_fuzzy = struct_time_to_date(e.upper_fuzzy()) self.lower_strict = struct_time_to_date(e.lower_strict()) self.upper_strict = struct_time_to_date(e.upper_strict()) if self.lower_strict.year != self.upper_strict.year: self.nat_lang_year = '{}-{}'.format(self.lower_strict.year, self.upper_strict.year) else: self.nat_lang_year = str(self.lower_strict.year) def save(self, *args, **kwargs): # If there's no preview score file, then just save the model if not self.preview_score: return super().save(*args, **kwargs) if self.preview_score_checked: # This was the cause of a subtle bug. Because this method can be # called multiple times during model creation, leaving this flag # set would cause the post save hook to fire multiple times. self.preview_score_updated = False return super().save(*args, **kwargs) h = hashlib.md5() for chunk in iter(lambda: self.preview_score.read(8192), b''): h.update(chunk) self.preview_score.seek(0) checksum = h.hexdigest() if not self.preview_score_checksum == checksum: self.preview_score_checksum = checksum self.preview_score_updated = True self.preview_score_checked = True return super().save(*args, **kwargs) content_panels = Page.content_panels + [ FieldPanel('composition_title'), StreamFieldPanel('description'), MultiFieldPanel( [FieldPanel('edtf_string'), FieldPanel('nat_lang_edtf_string')], help_text='Enter a date in the LOC Extended Date Time Format', heading='Date'), FieldPanel('location'), FieldPanel('instrumentation'), FieldPanel('orchestration'), FieldPanel('duration'), FieldPanel('dedicatee'), FieldPanel('premiere'), FieldPanel('genre'), FieldPanel('text_source'), FieldPanel('collaborator'), FieldPanel('manuscript_status'), FieldPanel('information_up_to_date'), FieldPanel('scanned'), FieldPanel('preview_score'), StreamFieldPanel('recording'), ] search_fields = Page.search_fields + [ index.SearchField('description', partial_match=True), index.SearchField('location', partial_match=True), index.SearchField('dedicatee', partial_match=True), index.SearchField('premiere', partial_match=True), index.SearchField('text_source', partial_match=True), index.SearchField('collaborator', partial_match=True), index.SearchField('manuscript_status', partial_match=True), index.SearchField('recording', partial_match=True), index.RelatedFields('genre', [ index.SearchField('genre_en', partial_match=True), index.SearchField('genre_fr', partial_match=True), ]), index.RelatedFields('instrumentation', [ index.SearchField('instrument_en', partial_match=True), index.SearchField('instrument_fr', partial_match=True), ]), ] parent_page_types = ['CompositionListingPage']
class User(AbstractBaseUser, PermissionsMixin): # Account info business_email = EmailField(unique=True) is_staff = BooleanField( verbose_name='staff status', default=False, help_text=('Designates whether the user can log into this admin ' 'site.')) is_active = BooleanField( verbose_name='active', default=True, help_text=('Designates whether this user should be treated as ' 'active. Unselect this instead of deleting accounts.')) date_joined = DateTimeField(default=timezone.now) # general contact information title = CharField(max_length=32) first_name = CharField(max_length=50) last_name = CharField(max_length=50) gender = CharField(max_length=6, choices=GENDER_CHOICES) contact_type = CharField(max_length=32, verbose_name="Type of Contact") # Address home_address = TextField(blank=True) business_address = TextField(blank=True) country = CharField(max_length=64, blank=True, choices=COUNTRIES, help_text=('The country in which the contact is ' 'currently working in')) nationality = CharField(max_length=64, blank=True, choices=NATIONALITIES) # Work job_title = CharField(max_length=64, blank=True) area_of_specialisation = CharField(max_length=128, blank=True) # Email personal_email = EmailField(blank=True) # IM skype_id = CharField(max_length=32, blank=True) yahoo_messenger = CharField(max_length=32, blank=True, verbose_name='Yahoo Messenger') msn_id = CharField(max_length=32, blank=True, verbose_name='MSN ID') # Phones & fax home_tel = CharField(max_length=20, blank=True, verbose_name="Home telephone") business_tel = CharField(max_length=20, blank=True, verbose_name="Business telephone") mobile = CharField(max_length=20, blank=True) fax = CharField(max_length=20, blank=True, verbose_name="Fax no") # Misc notes = TextField(blank=True) picture = ImageField(null=True, blank=True, upload_to=PictureUploadHandler('pictures')) cv = FileField(upload_to=UploadToHandler('pi_cvs', get_user_fields), blank=True, null=True) # Managers and book-keeping USERNAME_FIELD = 'business_email' REQUIRED_FIELDS = ['first_name', 'last_name'] objects = UserManager() def get_full_name(self): return u"{0} {1}".format(self.first_name, self.last_name) def get_short_name(self): return self.first_name def __unicode__(self): return self.get_full_name() @property def email(self): return self.business_email def save(self, *args, **kwargs): super(User, self).save(*args, **kwargs)
class Application(Model): owner = ForeignKey(verbose_name=_('owner'), to=User, on_delete=CASCADE) name = CharField(_('name'), max_length=150) description = TextField(_('description'), null=True, blank=True) client_id = CharField(_('client_id'), max_length=150) secret = CharField(_('client_secret'), max_length=150) logo = FileField(_('application_logo'), null=True, blank=True) allowed_callback_urls = TextField(_('allowed_callback_urls'), null=True, blank=True) allowed_web_origins = TextField(_('allowed_web_origins'), null=True, blank=True) allowed_logout_urls = TextField(_('allowed_logout_urls'), null=True, blank=True) expiration = PositiveIntegerField(_('expiration_in_seconds'), default=300) created_at = DateTimeField(_('when_created'), auto_now_add=True) deleted_at = DateTimeField(_('deleted_at'), null=True, blank=True) class Meta: verbose_name = _('Aplicação') verbose_name_plural = _('Aplicações') def __str__(self): return "%s [%s]" % (self.name, self.owner) def save(self, *args, **kwargs): if self.client_id is None or self.client_id == '' or self.secret is None or self.secret == '': self.client_id = hashlib.sha1(("%s" % uuid.uuid1()).encode('utf-8')).hexdigest() self.secret = hashlib.sha3_512(("%s" % uuid.uuid1()).encode('utf-8')).hexdigest() super().save(*args, **kwargs) @staticmethod def authorize(user, client_id, state, redirect_uri, referer): app = Application.validate_client_id(client_id) try: state = jwt.decode(state, app.secret, algorithm='HS512') except Exception as e: raise Exception("Invalid 'state'") assert 'client_id' in state, "state invalid encoded, client_id not present" assert 'uuid' in state, "state invalid encoded, uuid not present" # if referer is not None: # referer = referer # assert app.allowed_web_origins is None and validate_url(referer.split("?")[0], app.allowed_web_origins), \ # "'referer (%s)' not present on '%s (%s)'" % (referer.split("?")[0], # _('allowed_web_origins'), # app.allowed_web_origins) redirect_uri = unquote_plus(redirect_uri) assert validate_url(redirect_uri, app.allowed_callback_urls), \ "'redirect_uri' not present on '%s' - %s" % (_('allowed_callback_urls'), url_only(redirect_uri)) expire_at = make_aware(datetime.datetime.now() + datetime.timedelta(minutes=10)) hashcode = "%s" % uuid.uuid1() TransactionToken.objects.create(application=app, user=user, hashcode=hashcode, state=state, redirect_uri=redirect_uri, referer=referer, expire_at=expire_at) return hashcode @staticmethod def validate_client_id(client_id): try: return Application.objects.get(client_id=client_id, deleted_at__isnull=True) except Exception as e: raise Exception("Invalid 'client_id'")
class KidSite(Model): enabled = BooleanField() name = CharField(max_length=30, blank=True, null=True) url = URLField(null=True) icon = FileField(null=True)
class Topic(Page): resource_type = 'topic' parent_page_types = ['Topics'] subpage_types = ['Topic'] template = 'topic.html' # Content fields description = TextField(max_length=250, blank=True, default='') featured = StreamField( StreamBlock([ ('article', PageChooserBlock(required=False, target_model=( 'articles.Article', 'externalcontent.ExternalArticle', ))), ('external_page', FeaturedExternalBlock()), ], min_num=0, max_num=4, required=False), null=True, blank=True, ) tabbed_panels_title = CharField(max_length=250, blank=True, default='') tabbed_panels = StreamField( StreamBlock([('panel', TabbedPanelBlock())], min_num=0, max_num=3, required=False), null=True, blank=True, verbose_name='Tabbed panels', ) latest_articles_count = IntegerField( default=3, choices=((3, '3'), (6, '6'), (9, '9')), help_text='The number of articles to display for this topic.') # Card fields card_title = CharField('Title', max_length=140, blank=True, default='') card_description = TextField('Description', max_length=140, blank=True, default='') card_image = ForeignKey( 'mozimages.MozImage', null=True, blank=True, on_delete=SET_NULL, related_name='+', verbose_name='Image', ) # Meta icon = FileField(upload_to='topics/icons', blank=True, default='') color = CharField(max_length=14, choices=COLOR_CHOICES, default='blue-40') keywords = ClusterTaggableManager(through=TopicTag, blank=True) # Content panels content_panels = Page.content_panels + [ FieldPanel('description'), StreamFieldPanel('featured'), FieldPanel('tabbed_panels_title'), StreamFieldPanel('tabbed_panels'), FieldPanel('latest_articles_count'), MultiFieldPanel([ InlinePanel('people'), ], heading='People'), ] # Card panels card_panels = [ FieldPanel('card_title'), FieldPanel('card_description'), ImageChooserPanel('card_image'), ] # Meta panels meta_panels = [ MultiFieldPanel( [ InlinePanel('parent_topics', label='Parent topic(s)'), InlinePanel('child_topics', label='Child topic(s)'), ], heading='Parent/child topic(s)', classname='collapsible collapsed', help_text=( 'Topics with no parent (i.e. top-level topics) will be listed ' 'on the home page. Child topics are listed on the parent ' 'topic’s page.')), MultiFieldPanel([ FieldPanel('icon'), FieldPanel('color'), ], heading='Theme'), MultiFieldPanel([ FieldPanel('seo_title'), FieldPanel('search_description'), FieldPanel('keywords'), ], heading='SEO'), ] # Settings panels settings_panels = [ FieldPanel('slug'), FieldPanel('show_in_menus'), ] # Tabs edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(card_panels, heading='Card'), ObjectList(meta_panels, heading='Meta'), ObjectList(settings_panels, heading='Settings', classname='settings'), ]) @property def articles(self): return get_combined_articles(self, topics__topic__pk=self.pk) @property def events(self): """Return upcoming events for this topic, ignoring events in the past, ordered by start date""" return get_combined_events(self, topics__topic__pk=self.pk, start_date__gte=datetime.datetime.now()) @property def videos(self): """Return the latest videos and external videos for this topic. """ return get_combined_videos(self, topics__topic__pk=self.pk) @property def color_value(self): return dict(COLOR_VALUES)[self.color] @property def subtopics(self): return [topic.child for topic in self.child_topics.all()]
class Topic(BasePage): resource_type = "topic" parent_page_types = ["Topics"] subpage_types = ["Topic", "content.ContentPage"] template = "topic.html" # Content fields description = RichTextField( blank=True, default="", features=RICH_TEXT_FEATURES_SIMPLE, help_text="Optional short text description, max. 400 characters", max_length=400, ) featured = StreamField( StreamBlock( [ ( "post", PageChooserBlock(target_model=( "articles.Article", "externalcontent.ExternalArticle", )), ), ( "content_page", PageChooserBlock(target_model=("content.ContentPage", )), ), ("external_page", FeaturedExternalBlock()), ], min_num=2, max_num=7, required=True, ), null=True, blank=True, help_text="Optional space for featured items, max. 7", ) # "What We've Been Working On" panel recent_work = StreamField( StreamBlock( [ ( "post", PageChooserBlock(target_model=( "articles.Article", "externalcontent.ExternalArticle", )), ), ("external_page", FeaturedExternalBlock()), ( "video", PageChooserBlock( target_model=("videos.Video", "externalcontent.ExternalVideo")), ), ], max_num=4, required=False, ), null=True, blank=True, help_text= ("Optional space for featured posts, videos or links, min. 1, max. 4." ), ) # "Relevant Events" panel relevant_events = StreamField( StreamBlock( [( "event", PageChooserBlock( target_model=("events.Event", "externalcontent.ExternalEvent")), )], max_num=4, required=False, ), null=True, blank=True, help_text=("Optional space for featured Events, max. 4."), ) # Card fields card_title = CharField("Title", max_length=140, blank=True, default="") card_description = TextField("Description", max_length=400, blank=True, default="") card_image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", verbose_name="Image", help_text="An image in 16:9 aspect ratio", ) # Meta nav_description = TextField("Navigation description", max_length=400, blank=True, default="") icon = FileField( upload_to="topics/icons", blank=True, default="", help_text=("MUST be a black-on-transparent SVG icon ONLY, " "with no bitmap embedded in it."), validators=[check_for_svg_file], ) keywords = ClusterTaggableManager(through=TopicTag, blank=True) # Content panels content_panels = BasePage.content_panels + [ FieldPanel("description"), StreamFieldPanel("featured"), StreamFieldPanel("recent_work"), StreamFieldPanel("relevant_events"), MultiFieldPanel( [InlinePanel("people")], heading="Content by", help_text= "Optional list of people who create content on this topic", ), ] # Card panels card_panels = [ FieldPanel( "card_title", help_text=("Title displayed when this page is " "represented by a card in a list of items. " "If blank, the page's title is used."), ), FieldPanel( "card_description", help_text=("Summary text displayed when this page is " "represented by a card in a list of items. " "If blank, the page's description is used."), ), MultiFieldPanel( [ImageChooserPanel("card_image")], heading="16:9 Image", help_text= ("Image used for representing this page as a Card. " "Must be 16:9 aspect ratio. " "If not specified a fallback will be used. " "This image is also used for social media posts, unless overriden" ), ), ] # Meta panels meta_panels = [ FieldPanel( "nav_description", help_text= "Text to display in the navigation with the title for this page.", ), MultiFieldPanel( [ InlinePanel("parent_topics", label="Parent topic(s)"), InlinePanel("child_topics", label="Child topic(s)"), ], heading="Parent/child topic(s)", classname="collapsible collapsed", help_text=("Topics with no parent (i.e. top-level topics) will be " "listed on the home page. Child topics are listed " "on the parent topic’s page."), ), MultiFieldPanel([FieldPanel("icon")], heading="Theme"), MultiFieldPanel( [ FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("social_image"), FieldPanel("keywords"), ], heading="SEO", help_text=("Optional fields to override the default " "title and description for SEO purposes"), ), ] # Settings panels settings_panels = BasePage.settings_panels + [ FieldPanel("slug"), FieldPanel("show_in_menus"), ] # Tabs edit_handler = TabbedInterface([ ObjectList(content_panels, heading="Content"), ObjectList(card_panels, heading="Card"), ObjectList(meta_panels, heading="Meta"), ObjectList(settings_panels, heading="Settings", classname="settings"), ]) @property def articles(self): return get_combined_articles(self, topics__topic__pk=self.pk) def experts(self): """Return Person instances for topic experts""" return [person.person for person in self.people.all()] @property def videos(self): """Return the latest videos and external videos for this topic. """ return get_combined_videos(self, topics__topic__pk=self.pk) @property def subtopics(self): return [topic.child for topic in self.child_topics.all()] def get_section_background_panel_hints(self) -> dict: """When we have multiple optional sections on a Topic page, currently comprising 'recent_work', 'relevant_events' and 'experts', we want to ensure they are rendered on contrasting panels. This method (which is opinionated based on state, hence a method, not a function) returns a dictionary referencing the name of each panel and whether it should be on a tinted panel. """ output = { # Default state "recent_work": False, "relevant_events": False, "experts": False, } if self.recent_work: # If present, always takes a tint panel output["recent_work"] = True if self.relevant_events: output["relevant_events"] = not self.recent_work if self.experts: if not self.recent_work and not self.relevant_events: output["experts"] = True # Only panel, so tint it elif self.recent_work and self.relevant_events: output["experts"] = True # Third panel, so tint it else: output["experts"] = False return output
def test_filefield_generate_filename(self): f = FileField(upload_to='some/folder/') self.assertEqual(f.generate_filename(None, 'test with space.txt'), os.path.normpath('some/folder/test_with_space.txt'))
def test_FileField(self): lazy_func = lazy(lambda: 'filename.ext', str) self.assertIsInstance(FileField().get_prep_value(lazy_func()), str) lazy_func = lazy(lambda: 0, int) self.assertIsInstance(FileField().get_prep_value(lazy_func()), str)
class CommentHistoryExport(models.Model): request_user = ForeignKey(User, null=False, blank=False) mime_type = CharField(max_length=127, null=False, blank=False, db_index=True) file = FileField(upload_to='media/comment_history_dump/')
class Post(TimeStampedModel): author = ForeignKey(User, on_delete=SET_NULL, null=True) title = CharField(_("Post Title"), blank=False, null=True, max_length=255) slug = SlugField(unique=True, null=True, blank=True, max_length=500) videos = FileField( _("Upload Video"), upload_to=blog_file_path, null=True, blank=True, validators=[file_validator], ) url = URLField(blank=True, max_length=500, null=True, unique=True) pub_date = DateField( _("Post Published Date"), auto_now=False, auto_now_add=False, null=True, blank=False, ) draft = BooleanField(default=False) featured = BooleanField(default=False) content = RichTextUploadingField() comments = GenericRelation(Comment) added_by = CharField(_("added_by"), max_length=555, null=True, blank=True) categories = ManyToManyField("category.Category", help_text="Categorize this item.") tags = ManyToManyField("category.Tag", help_text="Tag this item.") objects = PostManager() def __str__(self): return self.title @staticmethod def autocomplete_search_fields(): return "title", "author" @property def readtime(self): return str(readtime.of_test(self.content)) @property def get_image_url(self): img = self.image_set.first() if img: return img.image.url return img #None class Meta: managed = True verbose_name = "Post" verbose_name_plural = "Posts" ordering = ["title", "-pub_date"] @property def get_related_posts_by_tags(self): return Post.objects.filter(tags__in=self.tags.all())[:4] # @property # def comments(self): # instance = self # return Comment.objects.filter_by_instance(instance) @property def get_content_type(self): instance = self content_type = ContentType.objects.get_for_model(instance.__class__) return content_type def get_absolute_url(self): return f"/blogs/{self.slug}" def get_update_url(self): return f"{self.get_absolute_url}/update" def get_delete_url(self): return f"{self.get_absolute_url}/delete"