Exemple #1
0
class MemberAdmin(admin.ModelAdmin):
    """Admin model for :class:`~groups.models.Member`."""
    inlines = (MemberRoleInline,)
    ordering = (Lower('name'),)
    list_display = ('name', '_twitter', 'discord', 'irc', '_reddit')
    search_fields = ('name', 'twitter', 'discord', 'irc', 'reddit')
    list_filter = (
        ('roles__group__name', filters.related_filter('group')),
        ('roles__role', filters.related_filter('role')),
    )

    def _twitter(self, obj: Member) -> str:
        if not obj.twitter:
            return ''
        return format_html(
            '<a href="https://twitter.com/{0}" rel="noopener noreferrer"'
            ' target="_blank">@{0}</a>', obj.twitter
        )

    _twitter.short_description = 'twitter'
    _twitter.admin_order_field = 'twitter'

    def _reddit(self, obj: Member) -> str:
        if not obj.reddit:
            return ''
        return format_html(
            '<a href="https://reddit.com/u/{0}" rel="noopener noreferrer"'
            ' target="_blank">/u/{0}</a>', obj.reddit
        )

    _reddit.short_description = 'reddit'
    _reddit.admin_order_field = 'reddit'
Exemple #2
0
class SeriesAdmin(admin.ModelAdmin):
    """Admin model for :class:`~reader.models.Series`."""
    form = SeriesForm
    inlines = (alias_inline('series'), )
    list_display = ('cover_image', 'title', 'created', 'modified', 'completed')
    list_display_links = ('title', )
    date_hierarchy = 'created'
    ordering = ('-modified', )
    search_fields = ('title', 'aliases__name')
    autocomplete_fields = ('categories', )
    list_filter = (('authors', filters.related_filter('author')),
                   ('artists', filters.related_filter('artist')),
                   ('categories', filters.related_filter('category')),
                   filters.boolean_filter('status', 'completed',
                                          ('Completed', 'Ongoing')))
    actions = ('toggle_completed', )
    empty_value_display = 'N/A'

    def cover_image(self, obj: Series) -> str:
        """
        Get the cover of the series as an HTML ``<img>``.

        :param obj: A ``Series`` model instance.

        :return: An ``<img>`` tag with the series cover.
        """
        return utils.img_tag(obj.cover, 'cover', height=75)

    cover_image.short_description = 'cover'

    def toggle_completed(self, request: 'HttpRequest', queryset: 'QuerySet'):
        """
        Toggle the status of the selected series.

        :param request: The original request.
        :param queryset: The original queryset.
        """
        queryset.update(completed=Q(completed=False))

    toggle_completed.short_description = 'Toggle status of selected series'
Exemple #3
0
class ChapterAdmin(admin.ModelAdmin):
    """Admin model for :class:`~reader.models.Chapter`."""
    inlines = (PageInline, )
    date_hierarchy = 'published'
    list_display = ('preview', 'title', 'volume', '_number', 'published',
                    'modified', 'final')
    list_display_links = ('title', )
    ordering = ('-modified', )
    search_fields = ('title', 'series__title')
    list_filter = (
        ('series', admin.RelatedFieldListFilter),
        ('groups', filters.related_filter('group')),
        filters.boolean_filter('status', 'final', ('Final', 'Not final')),
        ('published', DateFilter),
    )
    actions = ('toggle_final', )
    empty_value_display = 'N/A'

    def _number(self, obj: Chapter) -> str:
        return f'{obj.number:g}'

    _number.short_description = 'number'
    _number.admin_order_field = 'number'

    def preview(self, obj: Chapter) -> str:
        """
        Get the first image of the chapter as an HTML ``<img>``.

        :param obj: A ``Chapter`` model instance.

        :return: An ``<img>`` tag with the chapter preview.
        """
        page = obj.pages.only('image').first()
        if page is None:
            return ''
        return utils.img_tag(page.image, 'preview', height=50)

    def toggle_final(self, request: 'HttpRequest', queryset: 'QuerySet'):
        """
        Toggle the status of the selected chapters.

        :param request: The original request.
        :param queryset: The original queryset.
        """
        queryset.update(final=Q(final=False))

    toggle_final.short_description = 'Toggle status of selected chapters'
Exemple #4
0
class ChapterAdmin(admin.ModelAdmin):
    """Admin model for :class:`~reader.models.Chapter`."""
    inlines = (PageInline,)
    date_hierarchy = 'published'
    list_display = (
        'preview', 'title', 'series', 'volume', '_number',
        'published', 'modified', 'views', 'final'
    )
    list_display_links = ('title',)
    ordering = ('-modified',)
    sortable_by = (
        'title', 'series', 'volume', 'number',
        'published', 'modified', 'views'
    )
    search_fields = ('title', 'series__title')
    list_filter = (
        ('series', admin.RelatedFieldListFilter),
        ('groups', filters.related_filter('group')),
        filters.boolean_filter(
            'status', 'final', ('Final', 'Not final')
        ),
        ('published', DateFilter),
        ('series__manager', filters.related_filter('manager')),
    )
    actions = ('toggle_final',)
    empty_value_display = 'N/A'

    @admin.display(ordering='number', description='number')
    def _number(self, obj: Chapter) -> str:
        return f'{obj.number:g}'

    def preview(self, obj: Chapter) -> str:
        """
        Get the first image of the chapter as an HTML ``<img>``.

        :param obj: A ``Chapter`` model instance.

        :return: An ``<img>`` tag with the chapter preview.
        """
        page = obj.pages.only('image').first()
        if page is None:
            return ''
        return utils.img_tag(page._thumb, 'preview', height=50)

    @admin.display(description='Toggle status of selected chapters')
    def toggle_final(self, request: HttpRequest, queryset: QuerySet):
        """
        Toggle the status of the selected chapters.

        :param request: The original request.
        :param queryset: The original queryset.
        """
        queryset.update(final=Q(final=False))

    def get_form(self, request: HttpRequest, obj: Optional[Chapter],
                 **kwargs) -> ModelForm:  # pragma: no cover
        form = super().get_form(request, obj, **kwargs)
        if 'series' in form.base_fields and not request.user.is_superuser:
            form.base_fields['series'].queryset = \
                Series.objects.filter(manager_id=request.user.id)
        return form

    def has_change_permission(self, request: HttpRequest, obj:
                              Optional[Chapter] = None) -> bool:
        """
        Return ``True`` if editing the object is permitted.

        | Superusers can edit any chapter.
        | Scanlators can only edit chapters of series they manage.

        :param request: The original request.
        :param obj: A ``Chapter`` model instance.

        :return: ``True`` if the user is allowed to edit the chapter.
        """
        if request.user.is_superuser or obj is None:
            return True
        return obj.series.manager_id == request.user.id

    def has_delete_permission(self, request: HttpRequest, obj:
                              Optional[Chapter] = None) -> bool:
        """
        Return ``True`` if deleting the object is permitted.

        | Superusers delete edit any chapter.
        | Scanlators can only delete chapters of series they manage.

        :param request: The original request.
        :param obj: A ``Chapter`` model instance.

        :return: ``True`` if the user is allowed to delete the chapter.
        """
        if request.user.is_superuser or obj is None:
            return True
        return obj.series.manager_id == request.user.id
Exemple #5
0
class SeriesAdmin(admin.ModelAdmin):
    """Admin model for :class:`~reader.models.Series`."""
    inlines = (alias_inline('series'),)
    list_display = (
        'cover_image', 'title', 'manager', 'created',
        'modified', 'views', 'completed', 'licensed'
    )
    list_display_links = ('title',)
    date_hierarchy = 'created'
    ordering = ('-modified',)
    sortable_by = ('title', 'created', 'modified', 'views')
    search_fields = ('title', 'aliases__name')
    autocomplete_fields = ('categories',)
    list_filter = (
        ('authors', filters.related_filter('author')),
        ('artists', filters.related_filter('artist')),
        ('categories', filters.related_filter('category')),
        filters.boolean_filter(
            'status', 'completed', ('Completed', 'Ongoing')
        ),
        ('manager', filters.related_filter('manager')),
    )
    actions = ('toggle_completed', 'toggle_licensed')
    empty_value_display = 'N/A'

    def get_form(self, request: HttpRequest, obj: Optional[Series]
                 = None, change: bool = False, **kwargs) -> ModelForm:
        form = super().get_form(request, obj, change, **kwargs)
        if 'format' in form.base_fields:
            form.base_fields['format'].help_text = mark_safe('<br>'.join((
                'The format used to render the chapter names.'
                ' The following variables are available:',
                '<b>{title}</b>: The title of the chapter.',
                '<b>{volume}</b>: The volume of the chapter.',
                '<b>{number}</b>: The number of the chapter.',
                '<b>{date}</b>: The chapter\'s upload date (YYYY-MM-DD).',
                '<b>{series}</b>: The title of the series.'
            )))
        if 'manager' in form.base_fields:
            form.base_fields['manager'].initial = request.user.id
            if request.user.is_superuser:  # pragma: no cover
                form.base_fields['manager'].required = False
            else:  # pragma: no cover
                form.base_fields['manager'].widget = HiddenInput()
        return form

    def get_queryset(self, request: HttpRequest) -> QuerySet:
        return super().get_queryset(request).annotate(
            views=Sum('chapters__views', distinct=True)
        )

    @admin.display(ordering='views')
    def views(self, obj: Series) -> int:
        """
        Get the total views of all chapters of the series.

        :param obj: A ``Series`` model instance.

        :return: The sum of chapter views.
        """
        return getattr(obj, 'views') or 0

    @admin.display(description='cover')
    def cover_image(self, obj: Series) -> str:
        """
        Get the cover of the series as an HTML ``<img>``.

        :param obj: A ``Series`` model instance.

        :return: An ``<img>`` tag with the series cover.
        """
        return utils.img_tag(obj.cover, 'cover', height=75)

    @admin.display(description='Toggle status of selected series')
    def toggle_completed(self, request: HttpRequest, queryset: QuerySet):
        """
        Toggle the publication status of the selected series.

        :param request: The original request.
        :param queryset: The original queryset.
        """
        queryset.update(completed=Q(completed=False))

    @admin.display(description='Toggle licensing of selected series')
    def toggle_licensed(self, request: HttpRequest, queryset: QuerySet):
        """
        Toggle the licensing status of the selected series.

        :param request: The original request.
        :param queryset: The original queryset.
        """
        queryset.update(licensed=Q(licensed=False))

    def has_change_permission(self, request: HttpRequest, obj:
                              Optional[Series] = None) -> bool:
        """
        Return ``True`` if editing the object is permitted.

        | Superusers can edit any series.
        | Scanlators can only edit series they manage.

        :param request: The original request.
        :param obj: A ``Series`` model instance.

        :return: ``True`` if the user is allowed to edit the series.
        """
        if request.user.is_superuser or obj is None:
            return True
        return obj.manager_id == request.user.id

    def has_delete_permission(self, request: HttpRequest, obj:
                              Optional[Series] = None) -> bool:
        """
        Return ``True`` if deleting the object is permitted.

        | Superusers can delete any series.
        | Scanlators can only delete series they manage.

        :param request: The original request.
        :param obj: A ``Series`` model instance.

        :return: ``True`` if the user is allowed to delete the series.
        """
        if request.user.is_superuser or obj is None:
            return True
        return obj.manager_id == request.user.id
Exemple #6
0
class GroupAdmin(admin.ModelAdmin):
    """Admin model for :class:`~groups.models.Group`."""
    exclude = ('id',)
    ordering = (Lower('name'),)
    list_display = ('image', 'name', '_website', 'manager', 'description')
    search_fields = ('name', 'website', 'description')
    list_display_links = ('name',)
    list_filter = (
        ('manager', filters.related_filter('manager')),
    )
    empty_value_display = 'N/A'

    def get_form(self, request: 'HttpRequest', obj: Optional[Group]
                 = None, change: bool = False, **kwargs) -> 'ModelForm':
        form = super().get_form(request, obj, change, **kwargs)
        if 'manager' in form.base_fields:
            form.base_fields['manager'].initial = request.user.id
            if not request.user.is_superuser:  # pragma: no cover
                form.base_fields['manager'].widget = HiddenInput()
        return form

    def image(self, obj: Group) -> str:
        """
        Get the logo of the group as an HTML ``<img>``.

        :param obj: A ``Group`` model instance.

        :return: An ``<img>`` tag with the group's logo.
        """
        return utils.img_tag(obj.logo, 'logo', height=25)

    image.short_description = 'logo'

    def _website(self, obj: Group) -> str:
        if not obj.website:
            return ''
        return format_html(
            '<a href="{0}" rel="noopener noreferrer"'
            ' target="_blank">{0}</a>', obj.website
        )

    _website.short_description = 'website'
    _website.admin_order_field = 'website'

    def has_change_permission(self, request: 'HttpRequest', obj:
                              Optional[Group] = None) -> bool:
        """
        Return ``True`` if editing the object is permitted.

        | Superusers can edit any group.
        | Scanlators can only edit groups they manage.

        :param request: The original request.
        :param obj: A ``Group`` model instance.

        :return: ``True`` if the user is allowed to edit the group.
        """
        if request.user.is_superuser or obj is None:
            return True
        return obj.manager_id == request.user.id

    def has_delete_permission(self, request: 'HttpRequest', obj:
                              Optional[Group] = None) -> bool:
        """
        Return ``True`` if deleting the object is permitted.

        | Superusers can delete any group.
        | Scanlators can only delete groups they manage.

        :param request: The original request.
        :param obj: A ``Group`` model instance.

        :return: ``True`` if the user is allowed to delete the group.
        """
        if request.user.is_superuser or obj is None:
            return True
        return obj.manager_id == request.user.id