CRUDLFA+ Tutorial¶
About document¶
This document attempts to teach the patterns you can use, and at the same time go through every feature. The document strives to teach CRUDLFA+ as efficiently as possible. If it becomes too long, we will see how we refactor the document, until then, it serves as main documentation. Please contribute any modification you feel this document needs to fit its purpose.
About module¶
CRUDLFA+ strives to provide a modern UI for Django generic views out of the box, but all defaults should also be overiddable as conveniently as possible. It turns out that Django performs extremely well already, and pushing Django’s philosophy such as DRY as far as possible works very well for me.
Start a CRUDLFA+ project¶
Make sure you install all project dependencies:
pip install crudlfap[project]
And create a Django project:
django-admin startproject yourproject
Copy this settings.py which provides working settings for CRUDLFA+
and allows to control settings with environment variables:
from crudlfap.settings import * # noqa
BASE_DIR = Path(__file__).resolve().parent.parent
INSTALLED_APPS += [ # noqa
# CRUDLFA+ extras
'django_registration',
'crudlfap_registration',
# CRUDLFA+ examples
'crudlfap_example.artist',
'crudlfap_example.song',
'crudlfap_example.blog',
]
ROOT_URLCONF = os.path.dirname(__file__).split('/')[-1] + '.urls'
DATABASES = {
'default': {
'ENGINE': os.getenv('DB_ENGINE', 'django.db.backends.sqlite3'),
'NAME': os.getenv('DB_NAME', os.path.join(
os.path.dirname(__file__),
'..',
'db.sqlite3',
)),
'HOST': os.getenv('DB_HOST'),
'USER': os.getenv('DB_USER'),
'PASSWORD': os.getenv('DB_PASS'),
}
}
install_optional(OPTIONAL_APPS, INSTALLED_APPS) # noqa
install_optional(OPTIONAL_MIDDLEWARE, MIDDLEWARE) # noqa
AUTHENTICATION_BACKENDS += [ # noqa
'crudlfap_example.blog.crudlfap.AuthBackend',
]
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
},
},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'stream': sys.stdout,
'formatter': 'verbose'
},
},
'loggers': {
'': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': True,
},
},
}
And this urls.py:
from django.conf import settings
from django.urls import include, path, re_path
from crudlfap import shortcuts as crudlfap
urlpatterns = [
crudlfap.site.urlpattern,
path('auth/', include('django.contrib.auth.urls')),
path('bundles/', include('ryzom_django.bundle')),
]
# CRUDLFA+ extras
if 'crudlfap_registration' in settings.INSTALLED_APPS:
urlpatterns.append(
path(
'registration/',
include('django_registration.backends.activation.urls')
),
)
if 'debug_toolbar' in settings.INSTALLED_APPS and settings.DEBUG:
import debug_toolbar
urlpatterns += [
re_path(r'^__debug__/', include(debug_toolbar.urls)),
]
# PUBLIC DEMO mode
from django.contrib.auth.views import LoginView # noqa
from django.contrib.auth.models import User # noqa
def login_view(request, *args, **kwargs):
if request.method == 'POST':
for name in ('admin', 'staff', 'user'):
user = User.objects.filter(username=name).first()
if not user:
user = User(username=name, email='user@example.com')
if name == 'staff':
user.is_staff = True
if name == 'admin':
user.is_superuser = True
user.set_password(name)
user.save()
return LoginView.as_view()(request, *args, **kwargs)
urlpatterns.insert(0, path('auth/login/', login_view, name='login')) # noqa
from crudlfap_auth.html import * # noqa
@template('registration/login.html', App) # noqa
class DemoLogin(LoginFormViewComponent):
def to_html(self, *content, **context):
return super().to_html(
H3('Demo mode enabled'),
P('Login with either username/password of:'),
Ul(
Li('user/user: for user'),
Li('staff/staff: for staff'),
Li('admin/admin: for superuser (technical stuff should appear)'), # noqa
),
*content,
**context,
)
You may also install manually, but the procedure might change over time.
Create a crudlfap.py¶
You need to create a Django app like with ./manage.py startapp yourapp. It
creates a yourapp directory and you need to add yourapp to
settings.INSTALLED_APPS.
Then, you can start a yourapp/crudlfap.py file, where you can define model
CRUDs, hook into the rendering of CRUDLFA+ (ie. menus) and so on.
Define a Router¶
Register a CRUD with default views using Router.register()¶
Just add a crudlfap.py file in one of your installed apps, and the
DefaultConfig will autodiscover them, this example
shows how to enable the default CRUD for a custom model:
from crudlfap import shortcuts as crudlfap
from .models import Artist
crudlfap.Router(
Artist,
fields='__all__',
icon='record_voice_over',
).register()
In this case, the Router will get the views
it should serve from the CRUDLFAP_VIEWS
setting.
Custom view parameters with View.clone()¶
If you want to specify views in the router:
from crudlfap import shortcuts as crudlfap
from .models import Song
class SongMixin:
allowed_groups = 'any'
def get_exclude(self):
if not self.request.user.is_staff:
return ['owner']
return super().get_exclude()
class SongCreateView(SongMixin, crudlfap.CreateView):
def form_valid(self):
self.form.instance.owner = self.request.user
return super().form_valid()
class SongRouter(crudlfap.Router):
fields = '__all__'
icon = 'album'
model = Song
views = [
crudlfap.DeleteView.clone(SongMixin),
crudlfap.UpdateView.clone(SongMixin),
SongCreateView,
crudlfap.DetailView.clone(
authenticate=False,
),
crudlfap.ListView.clone(
authenticate=False,
filter_fields=['artist'],
search_fields=['artist__name', 'name'],
),
]
def get_queryset(self, view):
user = view.request.user
if user.is_staff or user.is_superuser:
return self.model.objects.all()
elif not user.is_authenticated:
return self.model.objects.none()
return self.model.objects.filter(owner=user)
SongRouter().register()
Using the clone() classmethod will
define a subclass on the fly with the given attributes.
URLs¶
The easiest configuration is to generate patterns from the default registry:
from crudlfap import shortcuts as crudlfap
urlpatterns = [
crudlfap.site.urlpattern
]
Or, to sit in /admin:
crudlfap.site.urlpath = 'admin'
urlpatterns = [
crudlfap.site.urlpattern,
# your patterns ..
]
Changing home page¶
CRUDLFA+ so far relies on Jinja2 and provides a configuration where it finds templates in app_dir/jinja2.
As such, a way to override the home page template is to create a directory “jinja2” in one of your apps - personnaly i add the project itself to INSTALLED_APPS, sorry if you have hard feelings about it but i love to do that, have a place to put project-specific stuff in general - and in the jinja2 directory create a crudlfap/home.html file.
You will also probably want to override crudlfap/base.html. But where it gets more interresting is when you replace the home view with your own. Example, still in urls.py:
from crudlfap import shortcuts as crudlfap
from .views import Dashboard # your view
crudlfap.site.title = 'Your Title' # used by base.html
crudlfap.site.urlpath = 'admin' # example url prefix
crudlfap.site.views['home'] = views.Dashboard
urlpatterns = [
crudlfap.site.get_urlpattern(),
]
So, there’d be other ways to acheive this but that’s how i like to do it.
Create route from view¶
The following example returns a Route as needed by
Router and
Registry:
Route.factory(
LoginView,
title=_('Login'),
title_submit=_('Login'),
title_menu=_('Login'),
menus=['main'],
redirect_authenticated_user=True,
authenticate=False,
icon='login',
has_perm=lambda self: not self.request.user.is_authenticated,
)
Useful to add external apps views to routers or site.views, prior to the menu hook feature.
Create list actions¶
List actions, such as the delete action, can be implemented as such:
class DeployMixin(crudlfap.ActionMixin):
style = ''
icon = 'send'
success_url_next = True
color = 'green'
form_class = forms.Form
permission_shortcode = 'send'
label = 'deploy'
def get_success_url(self):
return self.router['list'].reverse()
def has_perm_object(self):
return self.object.state == 'held'
class TransactionDeployView(DeployMixin, crudlfap.ObjectFormView):
def form_valid(self):
self.object.state = 'deploy'
self.object.save()
return super().form_valid()
class TransactionDeployMultipleView(DeployMixin, crudlfap.ObjectsFormView):
def form_valid(self):
self.object_list.filter(state='held').update(state='deploy')
return super().form_valid()
API¶
CRUDLFA+ now offers a REST API interface which is documented with swagger on
/api.
Usage¶
It requires to set the Content-Type: application/json header. For example:
import requests
response = requests.get(
f'http://localhost:8000/blockchain',
headers={'Content-Type': 'application/json'}
)
assert response.status_code == 200
blockchains = response.json()
POST requests require a CSRF token that you may obtain as a cookie with any GET request, but it is advised that you keep it up to date by using a request session.
import requests
session = requests.session()
session.get('http://localhost:8000')
response = session.post(
f'{site}/auth/login/',
dict(
username='user', password='user'
),
headers={'X-CSRFToken': session.cookies['csrftoken']},
allow_redirects=False,
)
assert response.status_code == 302
Then you can make more POST requests ie. to create an object:
response = session.post(
f'{site}/account/create',
json=dict(
blockchain=123,
),
headers={
'Content-Type': 'application/json',
'X-CSRFToken': session.cookies['csrftoken'],
}
)
assert response.status_code == 201
In case of error, it will return a 4xx response with the error details in a JSON response.
Customize the API¶
By default, views will call the router’s
serialize(obj, fields)() method. You may override it,
or just configure json_fields attribute. To
serialize each field, it will try to find get_FIELDNAME_json() method,
otherwise use the default
get_FIELDNAME_json(obj, field)() method which
you might has well override. It will use the related router to serialize any
related object.
Then, you may override both of these at the view level if you want.