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'
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
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'
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
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