Commit b2fa1210 authored by Jean-Baptiste Pasquier's avatar Jean-Baptiste Pasquier

Merge branch 'more-comments' into 'master'

Comments & README

See merge request !115
parents 48c16aeb 54d80799
Pipeline #7209 passed with stage
in 1 minute and 11 seconds
......@@ -4,6 +4,8 @@ This module is an add-on for Django REST Framework that serves a django model re
It aims at enabling people with little development skills to serve their own data, to be used with a LDP application.
Building a Startin' Blox application? Read this: https://git.happy-dev.fr/startinblox/devops/doc
## Requirements
* Django (known to work with django 1.11)
......@@ -26,7 +28,18 @@ $ pip install djangoldp
$ django-admin startproject myldpserver
```
3. Create your django model inside a file myldpserver/myldpserver/models.py
3. Add DjangoLDP to INSTALLED_APPS
```python
INSTALLED_APPS = [
...
# make sure all of your own apps are installed BEFORE DjangoLDP
'djangoldp.apps.DjangoldpConfig',
]
```
IMPORTANT: DjangoLDP will register any models which haven't been registered, with the admin. As such it is important to add your own apps above DjangoLDP, so that you can use custom Admin classes if you wish
4. 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)
......@@ -38,14 +51,14 @@ class Todo(Model):
deadline = models.DateTimeField()
```
3.1. Configure container path (optional)
4.1. Configure container path (optional)
By default it will be "todos/" with an S for model called Todo
```python
<Model>._meta.container_path = "/my-path/"
```
3.2. Configure field visibility (optional)
4.2. Configure field visibility (optional)
Note that at this stage you can limit access to certain fields of models using
```python
......@@ -64,7 +77,7 @@ 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.
4. Add a url in your urls.py:
5. Add a url in your urls.py:
```python
from django.conf.urls import url
......@@ -86,14 +99,23 @@ You could also only use this line in settings.py instead:
ROOT_URLCONF = 'djangoldp.urls'
```
5. In the settings.py file, add your application name at the beginning of the application list, and add the following lines
6. In the settings.py file, add your application name at the beginning of the application list, and add the following lines
```python
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
```
6. You can also register your model for the django administration site
* `LDP_RDF_CONTEXT` tells DjangoLDP where our RDF [ontology](https://www.w3.org/standards/semanticweb/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
* `DJANGOLDP_PACKAGES` defines which other [DjangoLDP packages](https://git.happy-dev.fr/startinblox/djangoldp-packages) we're using in this installation
* `SITE_URL` is the URL serving the site, e.g. `https://example.com/`
* `BASE_URL` may be different from SITE_URL, e.g. `https://example.com/app/`
7. You can also register your model for the django administration site
```python
from django.contrib import admin
......@@ -102,15 +124,15 @@ from .models import Todo
admin.site.register(Todo)
```
7. You then need to have your WSGI server pointing on myldpserver/myldpserver/wsgi.py
8. You then need to have your WSGI server pointing on myldpserver/myldpserver/wsgi.py
8. You will probably need to create a super user
9. You will probably need to create a super user
```bash
$ ./manage.py createsuperuser
```
9. If you have no CSS on the admin screens :
10. If you have no CSS on the admin screens :
```bash
$ ./manage.py collectstatic
......
......@@ -4,6 +4,7 @@ from django.conf import settings
from django.contrib import admin
from .models import LDPSource, Model
# automatically import selected DjangoLDP packages from settings
for package in settings.DJANGOLDP_PACKAGES:
try:
import_module('{}.admin'.format(package))
......@@ -18,6 +19,7 @@ for package in settings.DJANGOLDP_PACKAGES:
model_classes = {cls.__name__: cls for cls in Model.__subclasses__()}
# automatically register models with the admin panel (which have not been added manually)
for class_name in model_classes:
model_class = model_classes[class_name]
if not admin.site.is_registered(model_class):
......
......@@ -27,13 +27,17 @@ from djangoldp.permissions import LDPPermissions
class LDListMixin:
'''A Mixin used by the custom Serializers in this file'''
child_attr = 'child'
# converts primitive data representation to the representation used within our application
def to_internal_value(self, data):
try:
# if this is a container, the data will be stored in ldp:contains
data = data['ldp:contains']
except (TypeError, KeyError):
pass
if len(data) == 0:
return []
if isinstance(data, dict):
......@@ -43,6 +47,7 @@ class LDListMixin:
return [getattr(self, self.child_attr).to_internal_value(item) for item in data]
# converts internal representation to primitive data representation
def to_representation(self, value):
'''
Permission on container :
......@@ -53,7 +58,9 @@ class LDListMixin:
child_model = getattr(self, self.child_attr).Meta.model
except AttributeError:
child_model = value.model
parent_model = None
if isinstance(value, QuerySet):
value = list(value)
......@@ -61,11 +68,13 @@ class LDListMixin:
filtered_values = value
container_permissions = Model.get_permissions(child_model, self.context['request'].user, ['view', 'add'])
else:
# this is a container. Parent model is the containing object, child the model contained
try:
parent_model = Model.resolve_parent(self.context['request'].path)
except:
parent_model = child_model
# remove objects from the list which I don't have permission to view
filtered_values = list(
filter(lambda v: Model.get_permission_classes(v, [LDPPermissions])[0]().has_object_permission(
self.context['request'], self.context['view'], v), value))
......
......@@ -24,6 +24,8 @@ from djangoldp.permissions import LDPPermissions
get_user_model()._meta.rdf_context = {"get_full_name": "rdfs:label"}
# renders into JSONLD format by applying context to the data
# https://github.com/digitalbazaar/pyld
class JSONLDRenderer(JSONRenderer):
media_type = 'application/ld+json'
......@@ -38,15 +40,18 @@ class JSONLDRenderer(JSONRenderer):
data["@context"] = settings.LDP_RDF_CONTEXT
return super(JSONLDRenderer, self).render(data, accepted_media_type, renderer_context)
# https://github.com/digitalbazaar/pyld
class JSONLDParser(JSONParser):
media_type = 'application/ld+json'
def parse(self, stream, media_type=None, parser_context=None):
data = super(JSONLDParser, self).parse(stream, media_type, parser_context)
# compact applies the context to the data and makes it a format which is easier to work with
# see: http://json-ld.org/spec/latest/json-ld/#compacted-document-form
return jsonld.compact(data, ctx=settings.LDP_RDF_CONTEXT)
# an authentication class which exempts CSRF authentication
class NoCSRFAuthentication(SessionAuthentication):
def enforce_csrf(self, request):
return
......@@ -81,6 +86,7 @@ class LDPViewSetGenerator(ModelViewSet):
@classonlymethod
def urls(cls, **kwargs):
'''constructs urls list for model passed in kwargs'''
kwargs['model'] = cls.get_model(**kwargs)
model_name = kwargs['model']._meta.object_name.lower()
if kwargs.get('model_prefix'):
......@@ -93,12 +99,14 @@ class LDPViewSetGenerator(ModelViewSet):
name='{}-detail'.format(model_name)),
]
# append nested fields to the urls list
for field in kwargs.get('nested_fields') or cls.nested_fields:
urls.append(url('^' + detail_expr + field + '/', LDPNestedViewSet.nested_urls(field, **kwargs)))
return include(urls)
# LDPViewSetGenerator is a ModelViewSet (DRF) with methods to automatically generate model urls
class LDPViewSet(LDPViewSetGenerator):
"""An automatically generated viewset that serves models following the Linked Data Platform convention"""
fields = None
......@@ -109,6 +117,8 @@ class LDPViewSet(LDPViewSetGenerator):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# attach filter backends based on permissions classes, to reduce the queryset based on these permissions
# https://www.django-rest-framework.org/api-guide/filtering/#generic-filtering
if self.permission_classes:
for p in self.permission_classes:
if hasattr(p, 'filter_class') and p.filter_class:
......@@ -136,11 +146,13 @@ class LDPViewSet(LDPViewSetGenerator):
return self.build_serializer(meta_args, 'Write')
def build_serializer(self, meta_args, name_prefix):
# create the Meta class to associate to LDPSerializer, using meta_args param
if self.fields:
meta_args['fields'] = self.fields
else:
meta_args['exclude'] = self.exclude or ()
meta_class = type('Meta', (), meta_args)
from djangoldp.serializers import LDPSerializer
return type(LDPSerializer)(self.model._meta.object_name.lower() + name_prefix + 'Serializer', (LDPSerializer,),
{'Meta': meta_class})
......@@ -148,6 +160,7 @@ class LDPViewSet(LDPViewSetGenerator):
def create(self, request, *args, **kwargs):
serializer = self.get_write_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
response_serializer = self.get_serializer()
data = response_serializer.to_representation(serializer.instance)
......@@ -210,6 +223,7 @@ class LDPViewSet(LDPViewSetGenerator):
return super(LDPView, self).get_queryset(*args, **kwargs)
def dispatch(self, request, *args, **kwargs):
'''overriden dispatch method to append some custom headers'''
response = super(LDPViewSet, self).dispatch(request, *args, **kwargs)
response["Access-Control-Allow-Origin"] = request.META.get('HTTP_ORIGIN')
response["Access-Control-Allow-Methods"] = "GET,POST,PUT,PATCH,DELETE"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment