Skip to content

Commit

Permalink
feat: django-guardian support (#152)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasvinclav committed Oct 17, 2023
1 parent 9cb1be8 commit 69cc922
Show file tree
Hide file tree
Showing 15 changed files with 390 additions and 8 deletions.
46 changes: 39 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
![screenshot](https://github.com/unfoldadmin/django-unfold/assets/10785882/daef6e7e-e8a1-4142-8e4c-fa2a287978d2)

## Unfold Django Admin Theme
## Unfold Django Admin Theme <!-- omit from toc -->

[![Build](https://img.shields.io/github/actions/workflow/status/unfoldadmin/django-unfold/release.yml?style=for-the-badge)](https://github.com/unfoldadmin/django-unfold/actions?query=workflow%3Arelease)
[![PyPI - Version](https://img.shields.io/pypi/v/django-unfold.svg?style=for-the-badge)](https://pypi.org/project/django-unfold/)
Expand All @@ -13,7 +13,7 @@ Unfold is theme for Django admin incorporating most common practises for buildin
- repository with demo implementation at [github.com/unfoldadmin/formula](https://github.com/unfoldadmin/formula)
- Django & Next.js boilerplate implementing Unfold at [github.com/unfoldadmin/turbo](https://github.com/unfoldadmin/turbo)

## Features
## Features <!-- omit from toc -->

- **Visual**: provides new user interface based on Tailwind CSS framework
- **Sidebar:** simplifies definition of custom sidebar navigation with icons
Expand All @@ -29,7 +29,7 @@ Unfold is theme for Django admin incorporating most common practises for buildin
- **Colors:** possibility to override default color scheme
- **Django import / export:** default support for this popular application

## Table of Contents
## Table of contents <!-- omit from toc -->

- [Installation](#installation)
- [Configuration](#configuration)
Expand All @@ -47,6 +47,9 @@ Unfold is theme for Django admin incorporating most common practises for buildin
- [Filters](#filters)
- [Third party packages](#third-party-packages)
- [django-import-export](#django-import-export)
- [django-modeltranslation](#django-modeltranslation)
- [django-guardian](#django-guardian)
- [django-money](#django-money)
- [User Admin Form](#user-admin-form)
- [Adding Custom Styles and Scripts](#adding-custom-styles-and-scripts)
- [Project Level Tailwind Stylesheet](#project-level-tailwind-stylesheet)
Expand All @@ -69,6 +72,7 @@ INSTALLED_APPS = [
"unfold.contrib.filters", # optional, if special filters are needed
"unfold.contrib.forms", # optional, if special form elements are needed
"unfold.contrib.import_export", # optional, if django-import-export package is used
"unfold.contrib.guardian", # optional, if django-guardian package is used
"django.contrib.admin", # required
]
```
Expand Down Expand Up @@ -528,9 +532,7 @@ class YourModelAdmin(ModelAdmin):

### django-import-export

To get proper visual appearance for django-import-export, two things are needed

1. Add `unfold.contrib.import_export` to `INSTALLED_APPS` at the begging of the file. This action will override all templates coming from the plugin.
1. Add `unfold.contrib.import_export` to `INSTALLED_APPS` at the beggining of the file. This action will override all templates coming from the application.
2. Change `import_form_class` and `export_form_class` in ModelAdmin which is inheriting from `ImportExportModelAdmin`. This chunk of code is responsible for adding proper styling to form elements.

```python
Expand All @@ -544,6 +546,36 @@ class ExampleAdmin(ModelAdmin, ImportExportModelAdmin):
export_form_class = ExportForm
```

### django-modeltranslation

By default Unfold does not contain any specific implementation for django-modeltranslation and the application is partially supported. Basic behavior is supported except of tab navigation provided by django-modeltranslation. At the moment there are no plans in supporting this behavior.

For django-modeltranslation fields for spefic languages, it is possible to define custom flags which will appear as a suffix in field's label. It is recommended to use emojis as suffix.

```python
# settings.py

UNFOLD = {
"EXTENSIONS": {
"modeltranslation": {
"flags": {
"en": "🇬🇧",
"fr": "🇫🇷",
"nl": "🇧🇪",
},
},
},
}
```

### django-guardian

Adding support for django-guardian is quote straightforward in Unfold, just add `unfold.contrib.guardian` to `INSTALLED_APPS` at the beggining of the file. This action will override all templates coming from the django-guardian. Please note that **Object permissions** link is available in top right dropdown navigation.

### django-money

This application is supported in Unfold by default. It is not needed to add any other applications into `INSTALLED_APPS`. Unfold is recognizing special form widget coming from django-money and applying specific styling.

## User Admin Form

User's admin in Django is specific as it contains several forms which are requiring custom styling. All of these forms has been inherited and accordingly adjusted. In user admin class it is needed to use these inherited form classes to enable custom styling matching rest of the website.
Expand Down Expand Up @@ -681,7 +713,7 @@ Some components like datepickers, calendars or selectors in admin was not possib

None: most of the custom styles localted in style.css are created via `@apply some-tailwind-class;`.

# Credits
## Credits

- [TailwindCSS](https://tailwindcss.com/) - CSS framework
- [HTMX](https://htmx.org/) - AJAX communication with backend
Expand Down
Empty file.
6 changes: 6 additions & 0 deletions src/unfold/contrib/guardian/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class GuardianConfig(AppConfig):
name = "unfold.contrib.guardian"
label = "unfoldguardian"
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% extends "admin/change_form.html" %}

{% load i18n admin_urls %}

{% block object-tools-items %}
{% url opts|admin_urlname:'permissions' original.pk|admin_urlquote as history_url %}

<a href="{% add_preserved_filters history_url %}" class="permissionslink mx-1 px-3 py-2 rounded-md transition-all hover:bg-gray-100 hover:text-gray-700 dark:hover:bg-gray-700 dark:hover:text-gray-200">
{% trans "Object permissions" %}
</a>

{{ block.super }}
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{% load unfold %}

{% if field.is_checkbox %}
{{ field }}

<label for="{{ field.auto_id }}" class="required vCheckboxLabel">
{{ field.label }}
</label>
{% else %}
{% include "unfold/helpers/field.html" with field=field|add_css_class:form_classes.text_input %}
{% endif %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{% extends "admin/change_form.html" %}

{% load admin_urls i18n guardian_tags %}

{% block breadcrumbs %}{% if not is_popup %}
<div class="px-4 lg:px-12">
<div class="container mb-6 mx-auto -my-3 lg:mb-12">
<ul class="flex">
{% url 'admin:index' as link %}
{% trans 'Home' as name %}
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=name %}

{% url 'admin:app_list' app_label=opts.app_label as link %}
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=opts.app_config.verbose_name %}

{% url opts|admin_urlname:'changelist' as link %}
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=opts.verbose_name_plural|capfirst %}

{% url opts|admin_urlname:'change' object.pk|admin_urlquote as link %}
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=original|truncatewords:'18' %}

{% trans 'Object permissions' as name %}
{% include 'unfold/helpers/breadcrumb_item.html' with link='' name=name %}
</ul>
</div>
</div>
{% endif %}{% endblock %}

{% block content %}
{% include "unfold/guardian/user_form.html" %}

<hr class="my-12 dark:border-gray-800"/>

{% include "unfold/guardian/group_form.html" %}
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{% extends "admin/change_form.html" %}
{% load admin_urls i18n %}

{% block extrahead %}{{ block.super }}
{{ form.media }}
{% endblock %}

{% block breadcrumbs %}{% if not is_popup %}
<div class="px-4 lg:px-12">
<div class="container mb-6 mx-auto -my-3 lg:mb-12">
<ul class="flex">
{% url 'admin:index' as link %}
{% trans 'Home' as name %}
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=name %}

{% url 'admin:app_list' app_label=opts.app_label as link %}
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=opts.app_config.verbose_name %}

{% url opts|admin_urlname:'changelist' as link %}
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=opts.verbose_name_plural|capfirst %}

{% url opts|admin_urlname:'change' object.pk|admin_urlquote as link %}
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=original|truncatewords:'18' %}

{% trans 'Object permissions' as name %}
{% include 'unfold/helpers/breadcrumb_item.html' with link="../../" name=name %}

{% include 'unfold/helpers/breadcrumb_item.html' with link='' name=group_obj|truncatewords:"18" %}
</ul>
</div>
</div>
{% endif %}{% endblock %}

{% block content %}
<div class="border border-gray-200 overflow-hidden rounded-md p-3 shadow-sm dark:border-gray-800">
<form method="post">
{% csrf_token %}

{% trans "Object" as title %}
{% include "unfold/helpers/field_readonly.html" with title=title value=object %}

{% trans "Group" as title %}
{% include "unfold/helpers/field_readonly.html" with title=title value=group_obj %}

{% include "unfold/helpers/field.html" with field=form.permissions %}

<div class="bg-gray-50 border-t flex flex-row -m-3 p-3 dark:bg-white/[.02] dark:border-gray-800">
<div class="ml-auto">
{% trans "Save" as title %}
{% include "unfold/helpers/submit.html" with title=title %}
</div>
</div>
</form>
</div>
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{% extends "admin/change_form.html" %}

{% load admin_urls i18n %}

{% block extrahead %}{{ block.super }}
{{ form.media }}
{% endblock %}

{% block breadcrumbs %}{% if not is_popup %}
<div class="px-4 lg:px-12">
<div class="container mb-6 mx-auto -my-3 lg:mb-12">
<ul class="flex">
{% url 'admin:index' as link %}
{% trans 'Home' as name %}
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=name %}

{% url 'admin:app_list' app_label=opts.app_label as link %}
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=opts.app_config.verbose_name %}

{% url opts|admin_urlname:'changelist' as link %}
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=opts.verbose_name_plural|capfirst %}

{% url opts|admin_urlname:'change' object.pk|admin_urlquote as link %}
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=original|truncatewords:'18' %}

{% trans 'Object permissions' as name %}
{% include 'unfold/helpers/breadcrumb_item.html' with link="../../" name=name %}

{% include 'unfold/helpers/breadcrumb_item.html' with link='' name=user_obj|truncatewords:"18" %}
</ul>
</div>
</div>
{% endif %}{% endblock %}

{% block content %}
<div class="border border-gray-200 overflow-hidden rounded-md p-3 shadow-sm dark:border-gray-800">
<form method="post">
{% csrf_token %}

{% trans "Object" as title %}
{% include "unfold/helpers/field_readonly.html" with title=title value=object %}

{% trans "User" as title %}
{% include "unfold/helpers/field_readonly.html" with title=title value=user_obj %}

{% include "unfold/helpers/field.html" with field=form.permissions %}

<div class="bg-gray-50 border-t flex flex-row -m-3 p-3 dark:bg-white/[.02] dark:border-gray-800">
<div class="ml-auto">
{% trans "Save" as title %}
{% include "unfold/helpers/submit.html" with title=title %}
</div>
</div>
</form>
</div>
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{% load i18n %}

<form method="post">
{% csrf_token %}

<h2 class="font-medium mb-3 text-sm text-gray-700 dark:text-gray-200">
{% trans "Group permissions" %}
</h2>

{% if groups_perms.items %}
<table id="group-permissions" class="border-gray-200 border-spacing-none border-separate mb-6 text-gray-700 w-full dark:text-gray-400 lg:border lg:rounded-md lg:shadow-sm lg:dark:border-gray-800">
<thead class="hidden lg:table-header-group">
<tr>
<th class="align-middle font-medium px-3 py-2 text-left text-gray-400 text-sm">
{% trans "Group" %}
</th>

{% for perm in model_perms %}
<th class="align-middle font-medium px-3 py-2 text-left text-gray-400 text-sm">
{{ perm.name }}
</th>
{% endfor %}

<th class="align-middle font-medium px-3 py-2 text-right text-gray-400 text-sm">
{% trans "Action" %}
</th>
</tr>
</thead>

<tbody>
{% for group, group_perms in groups_perms.items %}
<tr class="block border mb-3 rounded-md shadow-sm lg:table-row lg:border-none lg:mb-0 lg:shadow-none dark:border-gray-800">
<th class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans "User" %}">
<span class="text-gray-700 dark:text-gray-200">
{{ group }}
</span>
</th>

{% for perm in model_perms %}
<td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{{ perm.name }}">
{% if perm.codename in group_perms %}
{% include "unfold/helpers/boolean.html" with value=False %}
{% else %}
{% include "unfold/helpers/boolean.html" with value=True %}
{% endif %}
</td>
{% endfor %}

<td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-right text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans "Action" %}">
<a href="group-manage/{{ group.id|safe }}/" class="hover:text-gray-700 dark:hover:text-white">
{% trans "Edit" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}

<div class="border border-gray-200 overflow-hidden rounded-md p-3 shadow-sm dark:border-gray-800">
{% for field in group_form %}
{% include "admin/guardian/model/field.html" %}
{% endfor %}

<div class="bg-gray-50 border-t flex flex-row -m-3 p-3 dark:bg-white/[.02] dark:border-gray-800">
<div class="ml-auto">
{% trans "Manage group" as title %}
{% include "unfold/helpers/submit.html" with title=title name="submit_manage_group" %}
</div>
</div>
</div>
</form>
Loading

0 comments on commit 69cc922

Please sign in to comment.