Example #1
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'
Example #2
0
class UserAdmin(admin.ModelAdmin):
    """Admin model for :class:`User`."""
    exclude = ('password', 'groups')
    list_display = (
        'username', '_email', 'full_name',
        'date_joined', 'is_active'
    )
    list_editable = ('is_active',)
    search_fields = ('username', 'email', 'first_name', 'last_name')
    list_filter = (
        boolean_filter('status', 'is_active', ('Active', 'Inactive')),
        UserTypeFilter,
    )
    ordering = ('username',)

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

    _email.short_description = 'e-mail address'
    _email.admin_order_field = 'email'

    def full_name(self, obj: User) -> str:
        """
        Get the full name of the user.

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

        :return: The user's full name.
        """
        return obj.get_full_name()

    full_name.admin_order_field = C('first_name', V(' '), 'last_name')

    def has_add_permission(self, request: 'HttpRequest') -> bool:
        """
        Return whether adding an ``User`` object is permitted.

        :param request: The original request.

        :return: Always returns ``False``.
        """
        return False
Example #3
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'
Example #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
Example #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