class BlogEntry(OpenGraphMixin, Page): """Page for a single blog entry.""" blog_date = models.DateField("Blog Entry Date") blog_author = models.CharField(max_length=255) blog_tags = ClusterTaggableManager(through=BlogEntryTag, blank=True) body = StreamField([ ('banner_image', common_blocks.BannerImage()), ('heading', blocks.CharBlock(classname="full title")), ('paragraph', blocks.RichTextBlock()), ('image', common_blocks.CaptionImageBlock()), ('h1', common_blocks.HeaderH1(classname="full title")), ('subhead', common_blocks.Subhead(classname="full title")), ('block_quote', common_blocks.BlockQuote()), ('call_to_action', common_blocks.CallToAction()), ('small_call_to_action', common_blocks.CTAButton()), ]) search_fields = Page.search_fields + [ index.SearchField('body'), ] content_panels = Page.content_panels + [ MultiFieldPanel([ FieldPanel('blog_date'), FieldPanel('blog_author'), FieldPanel('blog_tags'), ], heading="Blog information"), StreamFieldPanel('body'), ] def save(self, *args, **kwargs): """Override to have a more specific slug w/ date & title.""" self.slug = "{0}-{1}".format(self.blog_date.strftime("%Y-%m-%d"), slugify(self.title)) super().save(*args, **kwargs)
class BlogIndexPage(Page): page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+') intro = RichTextField(blank=True) api_fields = ( 'intro', 'related_links', ) search_fields = Page.search_fields + (index.SearchField('intro'), ) def get_blog_entries(self): # Get list of live blog pages that are descendants of this page entries = BlogEntryPage.objects.descendant_of(self).live() # Order by most recent date first entries = entries.order_by('-date') return entries def get_context(self, request): # Get blog entries entries = self.get_blog_entries() # Filter by tag tag = request.GET.get('tag') if tag: entries = entries.filter(tags__name=tag) paginator, entries = paginate(request, entries, page_key='page', per_page=10) # Update template context context = super(BlogIndexPage, self).get_context(request) context['entries'] = entries return context
class BlogIndexPage(Page): intro = RichTextField(blank=True) search_fields = Page.search_fields + (index.SearchField('intro'), ) @property def blogs(self): # Get list of live blog pages that are descendants of this page blogs = BlogPage.objects.live().descendant_of(self) # Order by most recent date first blogs = blogs.order_by('-date') return blogs def get_context(self, request): # Get blogs blogs = self.blogs # Filter by tag tag = request.GET.get('tag') if tag: blogs = blogs.filter(tags__name=tag) # Pagination page = request.GET.get('page') paginator = Paginator(blogs, 10) # Show 10 blogs per page try: blogs = paginator.page(page) except PageNotAnInteger: blogs = paginator.page(1) except EmptyPage: blogs = paginator.page(paginator.num_pages) # Update template context context = super(BlogIndexPage, self).get_context(request) context['blogs'] = blogs return context
class HomePage(Page): title_text = RichTextField(null=True, blank=True) feed_image = models.ForeignKey( Image, help_text="An optional image to represent the page", null=True, blank=True, on_delete=models.SET_NULL, related_name='+') body = StreamField([ ('HTML', HtmlBlock()), ('WYSIWYG', WysiwygBlock()), ('Row', RowBlock()), ('Hero', HeroBlock()), ('Hero_CTA', HeroCallToActionBlock()), ], null=True, blank=True) search_fields = Page.search_fields + (index.SearchField('body'), ) class Meta: verbose_name = "Homepage"
class HomePageMarketing(Orderable): page = ParentalKey('HomePage', related_name='home_market') market_title = models.CharField(max_length=300, null=True, blank=True) market_button = models.CharField(max_length=300, null=True, blank=True) market_url = models.URLField(null=True, blank=True) market_image = models.ForeignKey('wagtailimages.Image', blank=True, null=True, on_delete=models.SET_NULL, related_name='+') search_fields = Page.search_fields + [ index.SearchField('market_title'), # index.SearchField('sub_heading'), # index.SearchField('intro') ] panels = [ FieldPanel('market_title'), FieldPanel('market_url'), FieldPanel('market_button'), ImageChooserPanel('market_image') ]
class EventIndexPage(Page): page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+') intro = RichTextField(blank=True) api_fields = ( 'intro', 'related_links', ) search_fields = Page.search_fields + (index.SearchField('intro'), ) def get_events(self): # Get list of live event pages that are descendants of this page events = EventPage.objects.descendant_of(self).live() # Filter events list to get ones that are either # running now or start in the future events = events.filter(date_from__gte=date.today()) # Order by date events = events.order_by('date_from') return events
class NewsIndexPage(HeaderPageMixin, Page): template = 'news/index.html' content = StreamField(CORE_BLOCKS, blank=True) class Meta: verbose_name = 'News index' content_panels = Page.content_panels + HeaderPageMixin.content_panels + [ StreamFieldPanel('content'), ] search_fields = Page.search_fields + HeaderPageMixin.search_fields + [ index.SearchField('content'), ] parent_page_types = ['cms.NewsAndEventsPage'] subpage_types = ['NewsPage'] @property def news(self): news = NewsPage.objects.descendant_of(self) return news.order_by('-date')
class BlogPage(Page): intro = RichTextField() body = StreamField([ ('heading', blocks.CharBlock(classname="full title")), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ]) tags = ClusterTaggableManager(through=BlogPageTag, blank=True) date = models.DateField("Post date") feed_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') search_fields = Page.search_fields + [ index.SearchField('body'), ] @property def blog_index(self): # Find closest ancestor which is a blog index return self.get_ancestors().type(BlogIndexPage).last()
class EventIndexPage(Page): subpage_types = [ 'events.SimpleEventPage', 'events.MultidayEventPage', 'events.RecurringEventPage', 'events.CalendarPage' ] intro = RichTextField(blank=True) search_fields = Page.search_fields + (index.SearchField('intro'), ) @property def recurringEvents(self): events = RecurringEventPage.objects.live() return events @property def old_events(self): # Get list of live event pages that are descendants of this page events = Page.objects.live().descendant_of(self) # Filter events list to get ones that are either # running now or start in the future #events = events.filter(date_from__gte=dt.date.today()) # Order by date #events = events.order_by('date_from') return events content_panels = Page.content_panels + [ FieldPanel('intro', classname="full"), InlinePanel('related_links', label="Related links"), ] promote_panels = [ MultiFieldPanel(Page.promote_panels, "Common page configuration") ]
class Performance(Page): subtitle = models.CharField("Подзаголовок", blank=True, max_length=255) top_image = models.ForeignKey( "wagtailimages.Image", verbose_name="Заглавная картинка", blank=True, null=True, on_delete=models.SET_NULL, related_name="+", ) video = models.ForeignKey('theatre.YouTubeEmbedSnippet', verbose_name="Видео с YouTube", null=True, blank=True, related_name='+', on_delete=models.SET_NULL) description = RichTextField("Описание", blank=True) content_panels = Page.content_panels + [ FieldPanel('subtitle', classname='full'), ImageChooserPanel('top_image'), FieldPanel('description'), InlinePanel('gallery_items', label="Фотогалерея"), SnippetChooserPanel('video'), ] search_fields = Page.search_fields + [ index.SearchField('description'), ] objects = PageManager() scheduled = ScheduledPerformanceManager() class Meta: ordering = ["title"] verbose_name = "Спектакль" verbose_name_plural = "Спектакли"
class Product(Page): parent_page_types = ['longclawproducts.ProductIndex'] description = RichTextField() tags = ClusterTaggableManager(through=ProductTag, blank=True) search_fields = Page.search_fields + [ index.RelatedFields('tags', [ index.SearchField('name', partial_match=True, boost=10), ]), ] content_panels = Page.content_panels + [ FieldPanel('description'), InlinePanel('variants', label='Product variants'), InlinePanel('images', label='Product images'), FieldPanel('tags'), ] @property def first_image(self): return self.images.first() @property def price_range(self): ''' Calculate the price range of the products variants ''' ordered = self.variants.order_by('price') if ordered: return ordered.first().price, ordered.last().price else: return None, None @property def in_stock(self): ''' Returns True if any of the product variants are in stock ''' return any(self.variants.filter(stock__gt=0))
class NewsIndexPage(BasePage): """ Index page for intranet news stories. """ intro = RichTextField() content_panels = Page.content_panels + [FieldPanel('intro') ] + BasePage.content_panels subpage_types = ['news.NewsPage'] search_fields = BasePage.search_fields + [ index.SearchField('intro'), ] def get_context(self, request): context = super(NewsIndexPage, self).get_context(request) # need to add order_by('story_date') sticky_pages = get_stories(sticky=True) news_pages = get_stories() page = int(request.GET.get('page', 1)) news_pages = get_stories_by_page(page) prev_link = None if page > 1: prev_link = "%s?page=%s" % (request.path, str(page - 1)) next_link = None if get_story_count() > page * get_stories_by_page.page_length: next_link = "%s?page=%s" % (request.path, str(page + 1)) context['sticky_pages'] = sticky_pages context['news_pages'] = news_pages context['prev_link'] = prev_link context['next_link'] = next_link return context
class DocumentsIndexPage(Page): """ This is the index page for the Documents Gallery. It contains the links to Gallery pages. Gallery Page displays the gallery documents according to tags defined. """ intro = RichTextField(blank=True) search_fields = Page.search_fields + ( index.SearchField('intro'), ) feed_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) @property def children(self): return self.get_children().live() def get_context(self, request): # Get list of live Gallery pages that are descendants of this page pages = DocumentsPage.objects.live().descendant_of(self) # Update template context context = super(DocumentsIndexPage, self).get_context(request) context['pages'] = pages return context class Meta: verbose_name = "Documents Index Page"
class Producto(index.Indexed, Orderable, models.Model): categoria_producto = models.ForeignKey(SubCategoria) precio_producto = models.DecimalField(decimal_places=2, max_digits=6) tipo_moneda = models.ForeignKey(TipoMoneda) titulo = models.CharField(db_index=True, max_length=250) contenido = models.TextField() foto = models.FileField(upload_to='productos/fotos/%Y/%m/%d', blank=True) #: contacto nombre_contacto = models.CharField(db_index=True, max_length=100) email_contacto = models.EmailField(blank=True) licencia_contacto = models.CharField(blank=True, max_length=100, db_index=True) municipio_contacto = models.ForeignKey(Municipio) telefono_contacto = models.CharField(db_index=True, max_length=12, blank=True) publicado = models.DateTimeField(auto_now_add=True, db_index=True) class Meta: verbose_name = 'Producto' verbose_name_plural = 'Productos' panels = [ FieldPanel('categoria_producto', classname='full'), FieldPanel('titulo', classname='full'), ] search_fields = [ index.SearchField('titulo', partial_match=True), index.FilterField('categoria_producto'), index.FilterField('tipo_moneda'), index.FilterField('get_municipio_contacto_display'), ] def __str__(self): return self.titulo
class ContentArticle(Page): template = 'articles/article.html' date = models.DateField() body = StreamField([ ('paragraph', blocks.RichTextBlock(icon='edit', template='wagtail_blocks/paragraph.html')), ('image', ImageChooserBlock(icon='image', template='wagtail_blocks/image.html')), ('full_width_image', ImageChooserBlock(icon='image', template='wagtail_blocks/full_width_image.html')), ]) tags = ClusterTaggableManager(through=ArticleTag, blank=True) image = models.ForeignKey("wagtailimages.Image", null=True, blank=True, on_delete=models.SET_NULL, related_name="+") author = models.CharField(max_length=200, null=True, blank=True) search_fields = Page.search_fields + [ index.FilterField('title'), index.FilterField('author'), index.SearchField('body'), index.FilterField('date'), ] content_panels = Page.content_panels + [ FieldPanel('date'), FieldPanel('tags'), FieldPanel('author'), StreamFieldPanel('body'), InlinePanel('related_links', label="Related links"), ]
class JobIndexPage(Page): intro = RichTextField(blank=True) search_fields = Page.search_fields + (index.SearchField('intro'), ) @property def jobs(self): jobs = self.job.all() return jobs def serve(self, request): # Get jobs jobs = self.jobs # Pagination page = request.GET.get('page') paginator = Paginator(jobs, 10) # Show 10 jobs per page try: jobs = paginator.page(page) except PageNotAnInteger: jobs = paginator.page(1) except EmptyPage: jobs = paginator.page(paginator.num_pages) return render(request, self.template, { 'self': self, 'jobs': jobs, }) content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('intro', classname="full"), InlinePanel('content_block', label="Content block"), InlinePanel('job', label="Job"), ]
class BlogPost(Page): date = models.DateField('Publication Date') byline = models.CharField(max_length=40) body = StreamField([ ('heading', blocks.CharBlock()), ('rich_text', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ]) search_fields = Page.search_fields + [ index.SearchField('body'), ] content_panels = Page.content_panels + [ FieldPanel('date'), FieldPanel('byline'), StreamFieldPanel('body') ] parent_page_types = [ 'BlogIndexPage', ] # It doesn't make sense for BlogPosts to have subpages, and if they did # they would not be accessible through any of the navigation anyway. subpage_types = [] @property def preview(self): """Returns the first sentence of the post, with HTML tags stripped, for use as a preview blurb.""" body_text = striptags(' '.join([ child.value.source for child in self.body if child.block_type == 'rich_text' ])) sentences = body_text.split('.') return '.'.join(sentences[:1]) + '.'
class CatalogTilesPage(Page): description = RichTextField(blank=False, verbose_name='Опис розділу', default='ADD DESCRIPTION') def main_image(self): gallery_item = self.gallery_images.first() if gallery_item: return gallery_item.image else: return None def simple_pagination(self, elements=3): products = self.get_children() if len(products) == 0: return None if len(products) < elements + 1: return products return grouper(elements, products, fillvalue=None) def animation_time(self, start_time=0.0): for element in range(len(self.simple_pagination())): start_time = start_time + 0.2 yield start_time class Meta: verbose_name = "Сторінка-каталог у вигляді тайлів, для однотипної продукції (Каталог-тайли)" search_fields = Page.search_fields + [ index.SearchField('description'), ] content_panels = Page.content_panels + [ FieldPanel('description', classname="full"), InlinePanel('gallery_images', label='Галерея зображень') ]
class FloorPlanPage(PublicBasePage): """ Floor plan page model. """ def __str__(self): return '%s, %s' % (self.title, self.unit.location.parent_building) image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) subpage_types = ['public.StandardPage'] content_panels = Page.content_panels + [ ImageChooserPanel('image'), ] + PublicBasePage.content_panels search_fields = PublicBasePage.search_fields + [ index.SearchField('image'), ]
class NewsPage(Page): date = models.DateField("Post date") name = models.CharField(blank=True, max_length=250) image = models.ForeignKey('wagtailimages.Image', on_delete=models.SET_NULL, related_name='+', null=True, blank=True, verbose_name="Миниатюра") body = RichTextField(editor='tinymce', blank=True, verbose_name="Текст") search_fields = Page.search_fields + [ index.SearchField('body'), ] sidebar = RichTextField(editor='tinymce', blank=True) content_panels = Page.content_panels + [ FieldPanel('date'), FieldPanel('name'), ImageChooserPanel('image'), FieldPanel('body', classname="full"), FieldPanel('sidebar'), InlinePanel('related_pages', label="Связанные новости"), ]
class LearnPage(AbstractFilterPage): content = StreamField([ ('full_width_text', organisms.FullWidthText()), ('info_unit_group_25_75_only', organisms.InfoUnitGroup2575Only()), ('expandable_group', organisms.ExpandableGroup()), ('expandable', organisms.Expandable()), ('well', organisms.Well()), ('call_to_action', molecules.CallToAction()), ('email_signup', organisms.EmailSignUp()), ('video_player', organisms.VideoPlayer()), ('table_block', organisms.AtomicTableBlock(table_options={'renderer': 'html'})), ('feedback', v1_blocks.Feedback()), ], blank=True) edit_handler = AbstractFilterPage.generate_edit_handler( content_panel=StreamFieldPanel('content')) template = 'learn-page/index.html' objects = PageManager() search_fields = AbstractFilterPage.search_fields + [ index.SearchField('content') ]
class HomePage(Page, WithStreamField): search_fields = Page.search_fields + [ index.SearchField('body'), ] subpage_types = [ 'BlogIndexPage', 'EventIndexPage', 'IndexPage', 'NewsIndexPage', 'PastEventIndexPage', 'RichTextPage', 'ChapterIndexPage', 'PersonIndexPage' ] def get_chapters(self): return Chapter.objects.live() def get_events(self): # Events that have not ended. today = date.today() events = Event.objects.live().filter( date_from__gte=today).order_by('date_from')[:2] return events def get_news(self): news = NewsPost.objects.live().order_by('-date')[:2] return news
class BlogEntryPage(Page): body = RichTextField() tags = ClusterTaggableManager(through='BlogEntryPageTag', blank=True) date = models.DateField("Post date") feed_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') api_fields = ( 'body', 'tags', 'date', 'feed_image', 'carousel_items', 'related_links', ) search_fields = Page.search_fields + (index.SearchField('body'), ) def get_blog_index(self): # Find closest ancestor which is a blog index return BlogIndexPage.ancestor_of(self).last()
class AllEventIndexPage(RoutablePageMixin, Page, WithStreamField): search_fields = Page.search_fields + [ index.SearchField('body'), ] subpage_types = [] @property def events(self): events = Event.objects.live().order_by('-date_from') return events @route(r'^$') def all_events(self, request): events = self.events return render(request, self.get_template(request), { 'self': self, 'events': _paginate(request, events) }) @route(r'^tag/(?P<tag>[\w\- ]+)/$') def tag(self, request, tag=None): if not tag: # Invalid tag filter logger.error('Invalid tag filter') return self.all_posts(request) posts = self.posts.filter(tags__name=tag) return render( request, self.get_template(request), { 'self': self, 'events': _paginate(request, posts), 'filter_type': 'tag', 'filter': tag })
class NavPage(Page): # Database fields nav_title = RichTextField(default='') body = StreamField([('heading', blocks.CharBlock(classname="full title")), ('paragraph', blocks.RawHTMLBlock())]) date = models.DateField("Post date") feed_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') # Search index configuraiton search_fields = Page.search_fields + ( index.SearchField('body'), index.FilterField('date'), ) # Editor panels configuration content_panels = Page.content_panels + [ FieldPanel('nav_title'), FieldPanel('date'), StreamFieldPanel('body'), ] promote_panels = [ MultiFieldPanel(Page.promote_panels, "Common page configuration"), ImageChooserPanel('feed_image'), ] # Parent page / subpage type rules parent_page_types = ['SectionPage']
class Post(Page): cover = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) body = StreamField(PostStreamBlock()) intro = models.TextField(max_length=600) tags = ClusterTaggableManager(through=PostTag, blank=True) date = models.DateField(_('Post date')) search_fields = Page.search_fields + [ index.SearchField('body'), ] content_panels = Page.content_panels + [ FieldPanel('intro'), ImageChooserPanel('cover'), FieldPanel('tags'), StreamFieldPanel('body'), FieldPanel('date'), ]
class ServicesPage(Page): main_image = models.ForeignKey('torchbox.TorchboxImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name=_('main image')) heading = models.TextField(blank=True, verbose_name=_('heading')) intro = models.TextField(blank=True, verbose_name=_('intro')) search_fields = Page.search_fields + [ index.SearchField('intro'), ] content_panels = [ FieldPanel('title', classname='full title'), ImageChooserPanel('main_image'), FieldPanel('heading'), FieldPanel('intro', classname='full'), InlinePanel('services', label='Services'), ] class Meta: verbose_name = _("Service Page")
class HomePage(CFGOVPage): header = StreamField([ ('info_unit', molecules.InfoUnit()), ('half_width_link_blob', molecules.HalfWidthLinkBlob()), ], blank=True) # General content tab content_panels = CFGOVPage.content_panels + [ StreamFieldPanel('header'), InlinePanel( 'excluded_updates', label='Pages excluded from Latest Updates', help_text=('This block automatically displays the six most ' 'recently published blog posts, press releases, ' 'speeches, testimonies, events, or op-eds. If you want ' 'to exclude a page from appearing as a recent update ' 'in this block, add it as an excluded page.')), ] # Tab handler interface edit_handler = TabbedInterface([ ObjectList(content_panels, heading='General Content'), ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) # Sets page to only be createable at the root parent_page_types = ['wagtailcore.Page'] objects = PageManager() search_fields = CFGOVPage.search_fields + [index.SearchField('header')] @property def page_js(self): return super(HomePage, self).page_js + ['home-page.js'] def get_category_name(self, category_icon_name): cats = dict(ref.limited_categories) return cats[str(category_icon_name)] def get_context(self, request): context = super(HomePage, self).get_context(request) context.update({ 'carousel_items': self.get_carousel_items(), 'info_units': self.get_info_units(), 'latest_updates': self.get_latest_updates(request), }) return context def get_carousel_items(self): return _carousel_items_by_language[self.language] def get_info_units(self): return [ molecules.InfoUnit().to_python(info_unit) for info_unit in _info_units_by_language[self.language] ] def get_latest_updates(self, request): # TODO: There should be a way to express this as part of the query # rather than evaluating it in Python. excluded_updates_pks = [ e.excluded_page.pk for e in self.excluded_updates.all() ] latest_pages = CFGOVPage.objects.in_site( request.site).exclude(pk__in=excluded_updates_pks).filter( Q(content_type__app_label='v1') & (Q(content_type__model='blogpage') | Q(content_type__model='eventpage') | Q(content_type__model='newsroompage')), live=True, ).distinct().order_by('-first_published_at')[:6] return latest_pages def get_template(self, request): if flag_enabled('NEW_HOME_PAGE', request=request): return 'home-page/index_new.html' else: return 'home-page/index_%s.html' % self.language
class ContentPage(RelativeURLMixin, Page): body = StreamField(content_blocks) content_panels = Page.content_panels + [StreamFieldPanel('body')] search_fields = Page.search_fields + [index.SearchField('body')]
class AbstractMedia(CollectionMember, index.Indexed, models.Model): MEDIA_TYPES = ( ('audio', _('Audio file')), ('video', _('Video file')), ) title = models.CharField(max_length=255, verbose_name=_('title')) file = models.FileField(upload_to='media', verbose_name=_('file')) type = models.CharField(choices=MEDIA_TYPES, max_length=255, blank=False, null=False) duration = models.PositiveIntegerField(verbose_name=_('duration'), help_text=_('Duration in seconds')) width = models.PositiveIntegerField(null=True, blank=True, verbose_name=_('width')) height = models.PositiveIntegerField(null=True, blank=True, verbose_name=_('height')) thumbnail = models.FileField(upload_to='media_thumbnails', blank=True, verbose_name=_('thumbnail')) created_at = models.DateTimeField(verbose_name=_('created at'), auto_now_add=True) uploaded_by_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('uploaded by user'), null=True, blank=True, editable=False, on_delete=models.SET_NULL) tags = TaggableManager(help_text=None, blank=True, verbose_name=_('tags')) objects = MediaQuerySet.as_manager() search_fields = CollectionMember.search_fields + [ index.SearchField('title', partial_match=True, boost=10), index.RelatedFields('tags', [ index.SearchField('name', partial_match=True, boost=10), ]), index.FilterField('uploaded_by_user'), ] def __str__(self): return self.title @property def filename(self): return os.path.basename(self.file.name) @property def thumbnail_filename(self): return os.path.basename(self.thumbnail.name) @property def file_extension(self): return os.path.splitext(self.filename)[1][1:] @property def url(self): return self.file.url def get_usage(self): return get_object_usage(self) @property def usage_url(self): return reverse('wagtailmedia:media_usage', args=(self.id, )) def is_editable_by_user(self, user): from wagtailmedia.permissions import permission_policy return permission_policy.user_has_permission_for_instance( user, 'change', self) class Meta: abstract = True verbose_name = _('media')