-
Benoit Alessandroni authoredBenoit Alessandroni authored
User model requirements
When implementing authentication in your own application, you have two options:
- Using or extending DjangoLDP-Account, a DjangoLDP package modelling federated users
- Using your own user model & defining the authentication behaviour yourself
Please see the Authentication guide for full information
If you're going to use your own model then for federated login to work your user model must extend DjangoLDP.Model
, or define a urlid
field on the user model, for example:
urlid = LDPUrlField(blank=True, null=True, unique=True)
If you don't include this field, then all users will be treated as users local to your instance
The urlid
field is used to uniquely identify the user and is part of the Linked Data Protocol standard. For local users it can be generated at runtime, but for some resources which are from distant servers this is required to be stored
Creating your first model
- Create your django model inside a file myldpserver/myldpserver/models.py Note that container_path will be use to resolve instance iri and container iri In the future it could also be used to auto configure django router (e.g. urls.py)
from djangoldp.models import Model
class Todo(Model):
name = models.CharField(max_length=255)
deadline = models.DateTimeField()
1.1. Configure container path (optional) By default it will be "todos/" with an S for model called Todo
<Model>._meta.container_path = "/my-path/"
1.2. Configure field visibility (optional) Note that at this stage you can limit access to certain fields of models using
<Model>._meta.serializer_fields (<>list of field names to show>)
For example, if you have a model with a related field with type django.contrib.auth.models.User you don't want to show personal details or password hashes.
E.g.
from django.contrib.auth.models import User
User._meta.serializer_fields = ('username','first_name','last_name')
Note that this will be overridden if you explicitly set the fields= parameter as an argument to LDPViewSet.urls(), and filtered if you set the excludes= parameter.
- Add a url in your urls.py:
from django.conf.urls import url
from django.contrib import admin
from djangoldp.views import LDPViewSet
from .models import Todo
urlpatterns = [
url(r'^', include('djangoldp.urls')),
url(r'^admin/', admin.site.urls), # Optional
]
This creates 2 routes for each Model, one for the list, and one with an ID listing the detail of an object.
You could also only use this line in settings.py instead:
ROOT_URLCONF = 'djangoldp.urls'
- In the settings.py file, add your application name at the beginning of the application list, and add the following lines
STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'static')
LDP_RDF_CONTEXT = 'https://cdn.happy-dev.fr/owl/hdcontext.jsonld'
DJANGOLDP_PACKAGES = []
SITE_URL = 'http://localhost:8000'
BASE_URL = SITE_URL
-
LDP_RDF_CONTEXT
tells DjangoLDP where our RDF ontology is defined, which will be returned as part of our views in the 'context' field. This is a web URL and you can visit the value to view the full ontology online. The ontology can be a string, as in the example, but it can also be a dictionary, or a list of ontologies (see the JSON-LD spec for examples) -
DJANGOLDP_PACKAGES
defines which other DjangoLDP packages we're using in this installation -
SITE_URL
is the URL serving the site, e.g.https://example.com/
. Note that if you include the DjangoLDP urls in a nested path (e.g.https://example.com/api/
), thenSITE_URL
will need to be set to this value -
BASE_URL
may be different from SITE_URL, e.g.https://example.com/app/
- You can also register your model for the django administration site
from django.contrib import admin
from djangoldp.admin import DjangoLDPAdmin
from .models import Todo
admin.site.register(Todo, DjangoLDPAdmin)
-
You then need to have your WSGI server pointing on myldpserver/myldpserver/wsgi.py
-
You will probably need to create a super user
$ ./manage.py createsuperuser
- If you have no CSS on the admin screens :
$ ./manage.py collectstatic
Execution
To start the server, cd
to the root of your Django project and run :
$ python3 manage.py runserver
Using DjangoLDP
Models
To use DjangoLDP in your models you just need to extend djangoldp.Model
The Model class allows you to use your models in federation, adding a urlid
field, and some key methods useful in federation
If you define a Meta for your Model, you will need to explicitly inherit Model.Meta in order to inherit the default settings, e.g. default_permissions
from djangoldp.models import Model, LDPMetaMixin
class Todo(Model):
name = models.CharField(max_length=255)
class Meta(Model.Meta):
See "Custom Meta options" below to see some helpful ways you can tweak the behaviour of DjangoLDP
Your model will be automatically detected and registered with an LDPViewSet and corresponding URLs, as well as being registered with the Django admin panel. If you register your model with the admin panel manually, make sure to extend djangoldp.DjangoLDPAdmin so that the model is registered with Django-Guardian object permissions. An alternative version which extends Django's UserAdmin
is available as djangoldp.DjangoLDPUserAdmin
Model Federation
Model urlid
s can be local (matching settings.SITE_URL
), or external
To maintain consistency between federated servers, Activities such as Create, Update, Delete are sent to external resources referenced in a ForeignKey relation, instructing them on how to manage the reverse-links with the local server
This behaviour can be disabled in settings.py
SEND_BACKLINKS = False
It can also be disabled on a model instance
instance.allow_create_backlinks = False
LDPManager
DjangoLDP Models override models.Manager
, accessible by Model.objects
local()
For situations where you don't want to include federated resources in a queryset e.g.
Todo.objects.create(name='Local Todo')
Todo.objects.create(name='Distant Todo', urlid='https://anotherserversomewhere.com/todos/1/')
Todo.objects.all() # query set containing { Local Todo, Distant Todo }
Todo.objects.local() # { Local Todo } only
For Views, we also define a FilterBackend to achieve the same purpose. See the section on ViewSets for this purpose
LDPViewSet
DjangoLDP automatically generates ViewSets for your models, and registers these at urls, according to the settings configured in the model Meta (see below for options)
Custom Parameters
lookup_field
Can be used to use a slug in the url instead of the primary key.
LDPViewSet.urls(model=User, lookup_field='username')
nested_fields
list of ForeignKey, ManyToManyField, OneToOneField and their reverse relations. When a field is listed in this parameter, a container will be created inside each single element of the container.
In the following example, besides the urls /members/
and /members/<pk>/
, two others will be added to serve a container of the skills of the member: /members/<pk>/skills/
and /members/<pk>/skills/<pk>/
.
ForeignKey, ManyToManyField, OneToOneField that are not listed in the nested_fields
option will be rendered as a flat list and will not have their own container endpoint.
Meta:
nested_fields=["skills"]
Methods can be used to create custom read-only fields, by adding the name of the method in the serializer_fields
. The same can be done for nested fields, but the method must be decorated with a DynamicNestedField
.
LDPUser.circles = lambda self: Circle.objects.filter(members__user=self)
LDPUser.circles.field = DynamicNestedField(Circle, 'circles')
Configuring CSV export
DjangoLDP automaticallly provides CSV export on the admin site. By default, it exports the columns given in the list_display
attribute. This can be overridden with the attribute export_fields
. This setting can include fields of related object using __
.
class CustomAdmin(DjangoLDPAdmin):
export_fields = ['email', 'account__slug']
Improving Performance
On certain endpoints, you may find that you only need a subset of fields on a model, and serializing them all is expensive (e.g. if I only need the name
and id
of each group chat, then why serialize all of their members?). To optimise the fields serialized, you can pass a custom header in the request, Accept-Model-Fields
, with a list
value of desired fields e.g. ['@id', 'name']
Searching on LDPViewSets
It's common to allow search parameters on our ViewSet fields. Djangoldp provides automated searching on fields via the query parameters of a request via the class djangoldp.filters.SearchByQueryParamFilterBackend
, a FilterBackend applied by default to LDPViewSet
and any subclasses which don't override the filter_backends
property
To use this on a request, for example: /circles/?search-fields=name,description&search-terms=test&search-method=ibasic&search-policy=union
. For detail:
-
search-fields
: a list of one or more fields to search on the model -
search-terms
: the terms to search -
search-method
(optional): the method to apply the search with (supportsbasic
(contains), case-insensitiveibasic
andexact
) -
search-policy
(optional): the policy to apply when merging the results from different fields searched (union
, meaning include the union of all result sets. Orintersection
, meaning include only the results matched against all fields)
Some databases might treat accented characters as different from non-accented characters (e.g. grève vs. greve). To avoid this behaviour, please follow the Stackoverflow post here, and then add the setting SEARCH_UNACCENT_EXTENSION = True
and make sure that 'django.contrib.postgres'
is in your INSTALLED_APPS
.