├── examples
├── __init__.py
├── twitter_roa
│ ├── __init__.py
│ ├── urls.py
│ ├── manage.py
│ ├── admin.py
│ ├── models.py
│ ├── settings.py
│ └── serializers.py
├── django_roa_client
│ ├── __init__.py
│ ├── views.py
│ ├── urls.py
│ ├── forms.py
│ ├── manage.py
│ ├── admin.py
│ ├── serializers.py
│ ├── settings.py
│ ├── models.py
│ └── tests.py
├── django_rest_framework
│ ├── __init__.py
│ ├── backend
│ │ ├── backend
│ │ │ ├── __init__.py
│ │ │ ├── api
│ │ │ │ ├── urls.py
│ │ │ │ ├── __init__.py
│ │ │ │ ├── models.py
│ │ │ │ ├── views.py
│ │ │ │ ├── serializers.py
│ │ │ │ ├── fixtures
│ │ │ │ │ └── initial_data.json
│ │ │ │ └── mixins.py
│ │ │ ├── wsgi.py
│ │ │ ├── urls.py
│ │ │ └── settings.py
│ │ ├── load.sh
│ │ └── manage.py
│ ├── frontend
│ │ ├── frontend
│ │ │ ├── __init__.py
│ │ │ ├── urls.py
│ │ │ ├── wsgi.py
│ │ │ ├── serializers.py
│ │ │ ├── models.py
│ │ │ ├── settings.py
│ │ │ └── tests.py
│ │ └── manage.py
│ └── README.md
└── django_roa_server
│ ├── __init__.py
│ ├── fixtures
│ └── initial_data.json
│ ├── manage.py
│ ├── emitters.py
│ ├── settings.py
│ ├── models.py
│ ├── urls.py
│ └── handlers.py
├── django_roa
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ └── inspectresources.py
├── remoteauth
│ ├── __init__.py
│ ├── signals.py
│ ├── serializers.py
│ ├── backends.py
│ └── models.py
├── models.py
├── db
│ ├── middleware.py
│ ├── admin.py
│ ├── managers.py
│ ├── __init__.py
│ ├── exceptions.py
│ ├── query.py
│ └── models.py
└── __init__.py
├── req
└── tests.txt
├── .hgignore
├── .gitignore
├── THANKS
├── .travis.yml
├── .hgtags
├── setup.py
├── LICENSE
├── templates
└── admin
│ └── index.html
├── README.rst
├── CHANGELOG
└── ez_setup.py
/examples/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_roa/management/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_roa/remoteauth/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/twitter_roa/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/django_roa_client/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_roa/management/commands/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/backend/backend/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/backend/backend/api/urls.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/django_roa_server/__init__.py:
--------------------------------------------------------------------------------
1 | import emitters
2 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/backend/backend/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/frontend/frontend/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_roa/models.py:
--------------------------------------------------------------------------------
1 | # Otherwise Django do not consider this app for tests
2 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/backend/load.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | rm -f db.sqlite3
3 | python manage.py syncdb --noinput
4 | python manage.py runserver
5 |
--------------------------------------------------------------------------------
/req/tests.txt:
--------------------------------------------------------------------------------
1 | django-filter==0.9.2
2 | django-piston==0.2.3
3 | djangorestframework==2.4.4
4 | http-parser==0.8.3
5 | restkit==4.2.2
6 | simplejson==3.6.5
7 | socketpool==0.5.3
8 | wsgiref==0.1.2
9 |
--------------------------------------------------------------------------------
/django_roa/db/middleware.py:
--------------------------------------------------------------------------------
1 | from django_roa.db import set_roa_headers
2 |
3 |
4 | class ROAMiddleware(object):
5 | def process_request(self, request):
6 | # Set headers:
7 | set_roa_headers(request)
8 |
--------------------------------------------------------------------------------
/examples/twitter_roa/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import patterns, url, include
2 | from django.contrib import admin
3 |
4 | admin.autodiscover()
5 |
6 | urlpatterns = patterns('',
7 | url(r'^admin/', include(admin.site.urls)),
8 | )
9 |
--------------------------------------------------------------------------------
/.hgignore:
--------------------------------------------------------------------------------
1 | syntax: glob
2 |
3 | *.pyc
4 | *~
5 | *.db
6 | *.tmp
7 | *.DS_Store
8 | *.txt
9 | *.sqlite
10 | *.svn
11 | *.orig
12 | *.old
13 | *.swp
14 | settings.py
15 | manage.py
16 | django
17 | dist/*
18 | build/
19 | django_roa.egg-info/PKG-INFO
20 |
--------------------------------------------------------------------------------
/examples/django_roa_client/views.py:
--------------------------------------------------------------------------------
1 | from django.http import HttpResponse
2 |
3 | from django_roa_client.forms import RemotePageWithRelationsForm
4 |
5 | def home(request):
6 | form = RemotePageWithRelationsForm()
7 | return HttpResponse(form.as_ul())
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | *.pot
3 | *.pyc
4 | settings_local.py
5 | local_settings.py
6 | .idea/
7 | *.sql
8 | *.db
9 | .cache
10 | /static/
11 | .vagrant/
12 | examples/django_rest_framework/backend/admin
13 | examples/django_rest_framework/backend/db.sqlite3
14 |
--------------------------------------------------------------------------------
/examples/django_roa_client/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import patterns, url, include
2 | from django.contrib import admin
3 |
4 | from django_roa_client.views import home
5 |
6 | admin.autodiscover()
7 |
8 | urlpatterns = patterns('',
9 | url(r'^admin/', include(admin.site.urls)),
10 | url(r'^$', home),
11 | )
12 |
--------------------------------------------------------------------------------
/django_roa/remoteauth/signals.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.signals import user_logged_out
2 | from django.dispatch import receiver
3 |
4 |
5 | @receiver(user_logged_out)
6 | def logged_out_handler(sender, **kwargs):
7 | """
8 | User logout signal
9 | """
10 | from .backends import reset_api_auth
11 | reset_api_auth()
12 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/backend/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings")
7 |
8 | from django.core.management import execute_from_command_line
9 |
10 | execute_from_command_line(sys.argv)
11 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/frontend/frontend/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import patterns, include, url
2 |
3 | from django.contrib import admin
4 | admin.autodiscover()
5 |
6 | urlpatterns = patterns('',
7 | # Examples:
8 | # url(r'^$', 'frontend.views.home', name='home'),
9 | # url(r'^blog/', include('blog.urls')),
10 |
11 | url(r'^admin/', include(admin.site.urls)),
12 | )
13 |
--------------------------------------------------------------------------------
/THANKS:
--------------------------------------------------------------------------------
1 | Thank you guys!
2 | ===============
3 |
4 | My special thanks to Benoît Chesneau and Jesper Noehr for restkit and piston
5 | (respectively), their reactivity and thoughts on the project.
6 |
7 | Martin Skala made an impressive work on maintenance and improvements of the
8 | library in early 2012. This is highly rewarding and motivating as an
9 | Open-Source project.
10 |
11 | It has to be said.
12 |
--------------------------------------------------------------------------------
/django_roa/db/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib.admin.options import ModelAdmin as DjangoModelAdmin
2 | from django.contrib.admin.options import StackedInline as DjangoStackedInline, \
3 | TabularInline as DjangoTabularInline
4 |
5 | class ROAModelAdmin(DjangoModelAdmin):
6 | pass
7 |
8 | class ROAStackedInline(DjangoStackedInline):
9 | pass
10 |
11 | class ROATabularInline(DjangoTabularInline):
12 | pass
13 |
--------------------------------------------------------------------------------
/django_roa/remoteauth/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 | from .models import Group, Permission, User
3 |
4 |
5 | class PermissionSerializer(serializers.ModelSerializer):
6 | class Meta:
7 | model = Permission
8 |
9 |
10 | class GroupSerializer(serializers.ModelSerializer):
11 | class Meta:
12 | model = Group
13 |
14 |
15 | class UserSerializer(serializers.ModelSerializer):
16 | class Meta:
17 | model = User
18 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/backend/backend/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for backend project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings")
12 |
13 | from django.core.wsgi import get_wsgi_application
14 | application = get_wsgi_application()
15 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/frontend/frontend/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for frontend project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "frontend.settings")
12 |
13 | from django.core.wsgi import get_wsgi_application
14 | application = get_wsgi_application()
15 |
--------------------------------------------------------------------------------
/examples/django_roa_client/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django_roa_client.models import RemotePage, RemotePageWithRelations
3 |
4 | class TestForm(forms.Form):
5 | test_field = forms.CharField()
6 | remote_page = forms.ModelChoiceField(queryset=RemotePage.objects.all())
7 |
8 |
9 | class RemotePageForm(forms.ModelForm):
10 | class Meta:
11 | model = RemotePage
12 |
13 |
14 | class RemotePageWithRelationsForm(forms.ModelForm):
15 | class Meta:
16 | model = RemotePageWithRelations
17 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/frontend/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | # Add django_roa path:
6 | django_roa_path = os.path.abspath(os.path.join(
7 | os.path.dirname(os.path.abspath(__file__)),
8 | '../../../'
9 | ))
10 | sys.path.insert(0, django_roa_path)
11 |
12 |
13 | if __name__ == "__main__":
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "frontend.settings")
15 |
16 | from django.core.management import execute_from_command_line
17 |
18 | execute_from_command_line(sys.argv)
19 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/README.md:
--------------------------------------------------------------------------------
1 | How to run Django Rest Framework example tests:
2 | ===============================================
3 |
4 | Launch two terminals
5 |
6 | Backend part
7 | ------------
8 |
9 | In first terminal:
10 |
11 | $ cd examples/django_rest_framework/backend/
12 | $ bash load.sh
13 |
14 | Frontend part
15 | -------------
16 |
17 | In second terminal:
18 |
19 | $ cd examples/django_rest_framework/frontend/
20 | $ python manage.py test frontend
21 |
22 | After each test command, please re-run backend load command.
23 |
24 |
25 |
--------------------------------------------------------------------------------
/examples/django_roa_server/fixtures/initial_data.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "pk": 1,
4 | "model": "auth.user",
5 | "fields": {
6 | "username": "roa_user",
7 | "first_name": "",
8 | "last_name": "",
9 | "is_active": true,
10 | "is_superuser": true,
11 | "is_staff": true,
12 | "last_login": "2008-12-27 10:03:16",
13 | "groups": [],
14 | "user_permissions": [],
15 | "password": "sha1$436ec$7cd7f1c7057b3d4017267b53aabd35e3a29a80d6",
16 | "email": "roa_user@example.com",
17 | "date_joined": "2008-12-27 06:28:06"
18 | }
19 | }
20 | ]
21 |
--------------------------------------------------------------------------------
/django_roa/management/commands/inspectresources.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import NoArgsCommand
2 |
3 | class Command(NoArgsCommand):
4 | help = "Introspects the models and outputs a representation of resources."
5 |
6 | requires_model_validation = False
7 |
8 | def handle_noargs(self, **options):
9 | from django.db.models import get_models
10 | for model in get_models():
11 | if hasattr(model.objects, 'is_roa_manager'):
12 | print '%s (%s)' % (model.__name__, model.get_resource_url_list())
13 | for field in model._meta.fields:
14 | print ' %s (%s)' % (field.attname, field.__class__.__name__)
--------------------------------------------------------------------------------
/examples/twitter_roa/manage.py:
--------------------------------------------------------------------------------
1 | import sys, os
2 | sys.path.insert(0, os.path.split(os.path.split(os.getcwd())[0])[0])
3 |
4 | from django.core.management import execute_manager
5 |
6 | try:
7 | import settings # Assumed to be in the same directory.
8 | except ImportError:
9 | import sys
10 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
11 | sys.exit(1)
12 |
13 | if __name__ == "__main__":
14 | execute_manager(settings)
15 |
--------------------------------------------------------------------------------
/examples/django_roa_client/manage.py:
--------------------------------------------------------------------------------
1 | import sys, os
2 | sys.path.insert(0, os.path.split(os.path.split(os.getcwd())[0])[0])
3 |
4 | from django.core.management import execute_manager
5 |
6 | try:
7 | import settings # Assumed to be in the same directory.
8 | except ImportError:
9 | import sys
10 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
11 | sys.exit(1)
12 |
13 | if __name__ == "__main__":
14 | execute_manager(settings)
15 |
--------------------------------------------------------------------------------
/examples/django_roa_server/manage.py:
--------------------------------------------------------------------------------
1 | import sys, os
2 | sys.path.insert(0, os.path.split(os.path.split(os.getcwd())[0])[0])
3 |
4 | from django.core.management import execute_manager
5 |
6 | try:
7 | import settings # Assumed to be in the same directory.
8 | except ImportError:
9 | import sys
10 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
11 | sys.exit(1)
12 |
13 | if __name__ == "__main__":
14 | execute_manager(settings)
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 |
3 | python:
4 | - "2.6"
5 | - "2.7"
6 |
7 | env:
8 | - DJANGO_VERSION=1.4.15
9 | - DJANGO_VERSION=1.5.10
10 | - DJANGO_VERSION=1.6.7
11 | - DJANGO_VERSION=1.7
12 |
13 | matrix:
14 | exclude:
15 | - python: "2.6"
16 | env: DJANGO_VERSION=1.7
17 |
18 | install:
19 | - pip install --download-cache=".pip_download_cache" -q Django==$DJANGO_VERSION
20 | - pip install --download-cache=".pip_download_cache" -r req/tests.txt
21 | - python setup.py -q install
22 |
23 | script:
24 | - cd examples/django_rest_framework/backend/
25 | - bash load.sh &
26 | - sleep 4 # wait for runserver init
27 | - cd ../frontend/
28 | - python manage.py test frontend
29 |
--------------------------------------------------------------------------------
/django_roa/db/managers.py:
--------------------------------------------------------------------------------
1 | from django.db.models.manager import Manager
2 |
3 | from django_roa.db.query import RemoteQuerySet
4 |
5 |
6 | class ROAManager(Manager):
7 | """
8 | Manager which access remote resources.
9 | """
10 | use_for_related_fields = True
11 | is_roa_manager = True # ugly but useful because isinstance is evil
12 |
13 | def get_query_set(self):
14 | """
15 | Returns a QuerySet which access remote resources.
16 | """
17 | return RemoteQuerySet(self.model)
18 |
19 | def get_queryset(self):
20 | return RemoteQuerySet(self.model)
21 |
22 | def search(self, *args, **kwargs):
23 | return self.get_queryset().search(*args, **kwargs)
24 |
--------------------------------------------------------------------------------
/examples/django_roa_client/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django_roa import ModelAdmin
3 | from models import RemotePage, RemotePageWithManyFields, RemotePageWithCustomSlug
4 |
5 | class RemotePageAdmin(ModelAdmin):
6 | list_per_page = 2
7 |
8 |
9 | class RemotePageWithManyFieldsAdmin(ModelAdmin):
10 | list_display = ('__unicode__', 'date_field', 'char_field')
11 | list_filter = ('date_field',)
12 |
13 |
14 | class RemotePageWithCustomSlugAdmin(ModelAdmin):
15 | prepopulated_fields = {"slug": ("title",)}
16 |
17 | admin.site.register(RemotePage, RemotePageAdmin)
18 | admin.site.register(RemotePageWithManyFields, RemotePageWithManyFieldsAdmin)
19 | admin.site.register(RemotePageWithCustomSlug, RemotePageWithCustomSlugAdmin)
20 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/backend/backend/api/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 |
4 | # Declare a model with all relationships
5 |
6 |
7 | class Account(models.Model):
8 | email = models.CharField(max_length=30)
9 |
10 |
11 | class Reporter(models.Model):
12 | account = models.OneToOneField(Account, related_name='reporter')
13 | first_name = models.CharField(max_length=30)
14 | last_name = models.CharField(max_length=30)
15 |
16 |
17 | class Article(models.Model):
18 | headline = models.CharField(max_length=100)
19 | pub_date = models.DateField()
20 | reporter = models.ForeignKey(Reporter, related_name='articles')
21 |
22 |
23 | class Tag(models.Model):
24 | label = models.CharField(max_length=30)
25 | articles = models.ManyToManyField(Article, related_name='tags')
26 |
--------------------------------------------------------------------------------
/.hgtags:
--------------------------------------------------------------------------------
1 | 81b112d277483c1f751debd70ba32febede2f865 0.1
2 | 195a60205a95afeca6f80432d701df8f219f5591 0.2
3 | efb28c5ea1f4791d88db6b71341ccf04bacf12b9 0.3
4 | a1d85028f3c09a39c2f4872fbac01d73388496ad 0.4
5 | 062ea60a4ac8f0c183522687de7888d7a3ba5c3a 0.5
6 | aded008eafcd9beb3732f00229514463129f7978 0.6
7 | 3472cf46793f94c079cea93298dda0d5baafa284 0.7
8 | 44ca10ae1a4970d2c2a57003f065f78514036486 0.8
9 | 382e0d19811c9398f6f6d1778164efc523dbd94f 0.9
10 | 7bdc4baad643054e20b04508da2948dcaf762b32 1.0
11 | f67107509ea1e070ece1dcf880c42d863e1789c6 1.1
12 | d6fa2e4e420bc1244103edddeeafde68c9b4e96d 1.2
13 | 284eed2d8a4be775bc463bdbdbb98ae5763130b7 1.3
14 | 3481b645c0579786880cbe26ad17a39ad9640dce 1.4
15 | 7eff422cf80858a22ce70e6b011713cd4a21f25a 1.5
16 | 903692084a61698bc90ba5dc7219201683de7dea 1.6
17 | e350e16c436456d13afe872bcdffb0d38848986b 1.7
18 |
--------------------------------------------------------------------------------
/examples/twitter_roa/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django_roa import ModelAdmin
3 | from models import Tweet, User
4 |
5 | class TweetAdmin(ModelAdmin):
6 | list_display = ('id', 'text', 'get_source_for_admin', 'get_user_link')
7 |
8 | def get_source_for_admin(self, item):
9 | return item.source
10 | get_source_for_admin.short_description = u'Source'
11 | get_source_for_admin.allow_tags = True
12 |
13 | def get_user_link(self, item):
14 | url = '../user/%s/' % (item.user.id)
15 | return '%s' % (url, item.user)
16 | get_user_link.short_description = u'User'
17 | get_user_link.allow_tags = True
18 |
19 | admin.site.register(Tweet, TweetAdmin)
20 |
21 | class UserAdmin(ModelAdmin):
22 | list_display = ('screen_name',)
23 |
24 | admin.site.register(User, UserAdmin)
25 |
--------------------------------------------------------------------------------
/django_roa/db/__init__.py:
--------------------------------------------------------------------------------
1 | from threading import local
2 | from django.conf import settings
3 |
4 |
5 | ROA_SESSION_HEADERS_KEY = 'roa_session_headers_key'
6 |
7 |
8 | # Current thread access token:
9 | _roa_headers = local()
10 |
11 |
12 | def set_roa_headers(request, headers=None):
13 |
14 | if headers:
15 | request.session[ROA_SESSION_HEADERS_KEY] = headers
16 | elif ROA_SESSION_HEADERS_KEY in request.session:
17 | headers = request.session[ROA_SESSION_HEADERS_KEY]
18 | else:
19 | headers = {}
20 |
21 | # Save it into current thread
22 | _roa_headers.value = headers
23 |
24 |
25 | def get_roa_headers():
26 | headers = getattr(settings, 'ROA_HEADERS', {}).copy()
27 | thread_headers = getattr(_roa_headers, 'value', {})
28 | headers.update(thread_headers)
29 | return headers
30 |
31 |
32 | def reset_roa_headers():
33 | if hasattr(_roa_headers, 'value'):
34 | del _roa_headers.value
35 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/backend/backend/urls.py:
--------------------------------------------------------------------------------
1 | from rest_framework import routers
2 | from .api.views import AccountViewSet, ReporterViewSet, ArticleViewSet, \
3 | TagViewSet, ReversedArticleViewSet, ReversedReporterViewSet, \
4 | ReversedTagViewSet, ReversedAccountViewSet
5 |
6 |
7 | # API
8 | router = routers.DefaultRouter()
9 | router.register(r'accounts', AccountViewSet, base_name='account')
10 | router.register(r'reporters', ReporterViewSet, base_name='reporter')
11 | router.register(r'articles', ArticleViewSet, base_name='article')
12 | router.register(r'tags', TagViewSet, base_name='tag')
13 | router.register(r'reversed/tags', ReversedTagViewSet, base_name='reversedtag')
14 | router.register(r'reversed/articles', ReversedArticleViewSet, base_name='reversedarticle')
15 | router.register(r'reversed/reporters', ReversedReporterViewSet, base_name='reversedreporter')
16 | router.register(r'reversed/accounts', ReversedAccountViewSet, base_name='reversedaccount')
17 |
18 | urlpatterns = router.urls
19 |
--------------------------------------------------------------------------------
/examples/django_roa_server/emitters.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from django.conf import settings
4 |
5 | from piston.emitters import Emitter, DjangoEmitter
6 |
7 | logger = logging.getLogger("django_roa_server")
8 |
9 | DEFAULT_CHARSET = getattr(settings, 'DEFAULT_CHARSET', 'utf-8')
10 |
11 | class ROADjangoEmitter(DjangoEmitter):
12 | """
13 | ROA Django XML emitter.
14 |
15 | Just log the response with logging module.
16 | """
17 | def render(self, request):
18 | response = super(ROADjangoEmitter, self).render(request, 'xml')
19 | logger.debug(u"Response:\n%s" % str(response).decode(DEFAULT_CHARSET))
20 | return response
21 |
22 | Emitter.register('django', ROADjangoEmitter, 'application/xml; charset=utf-8')
23 |
24 | class CustomDjangoEmitter(DjangoEmitter):
25 | """
26 | Custom Django XML emitter.
27 |
28 | Use a custom serializer.
29 | """
30 | def render(self, request):
31 | response = super(CustomDjangoEmitter, self).render(request, 'custom')
32 | logger.debug(u"Response:\n%s" % response.decode(DEFAULT_CHARSET))
33 | return response
34 |
35 | Emitter.register('custom', CustomDjangoEmitter, 'application/xml; charset=utf-8')
36 |
--------------------------------------------------------------------------------
/examples/django_roa_client/serializers.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.utils.xmlutils import SimplerXMLGenerator
3 |
4 | from django.core.serializers.xml_serializer import Serializer as XMLSerializer, \
5 | Deserializer as XMLDeserializer
6 |
7 | DEFAULT_CHARSET = getattr(settings, 'DEFAULT_CHARSET', 'utf-8')
8 |
9 | class Serializer(XMLSerializer):
10 | """
11 | A test serializer which removes ``django-objects`` xml tag from default
12 | Django's xml serializer, adapt it to your own usage.
13 | """
14 |
15 | def start_serialization(self):
16 | """
17 | Start serialization -- open the XML document and the root element.
18 | """
19 | self.xml = SimplerXMLGenerator(self.stream, self.options.get("encoding", DEFAULT_CHARSET))
20 | self.xml.startDocument()
21 | self.xml.startElement("django-test", {"version" : "1.0"})
22 |
23 | def end_serialization(self):
24 | """
25 | End serialization -- end the document.
26 | """
27 | self.indent(0)
28 | self.xml.endElement("django-test")
29 | self.xml.endDocument()
30 |
31 |
32 | class Deserializer(XMLDeserializer):
33 | pass
34 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | try:
5 | from setuptools import setup, find_packages
6 | except ImportError:
7 | import ez_setup
8 | ez_setup.use_setuptools()
9 | from setuptools import setup, find_packages
10 |
11 | setup(
12 | name='django-roa',
13 | version='1.8.1',
14 | url='https://github.com/charles-vdulac/django-roa',
15 | download_url='https://github.com/charles-vdulac/django-roa/archive/master.zip',
16 | license='BSD',
17 | description="Turn your models into remote resources that you can access through Django's ORM.",
18 | author='David Larlet',
19 | author_email='david@larlet.fr',
20 | packages=find_packages(),
21 | include_package_data=True,
22 | classifiers=[
23 | 'Development Status :: 4 - Beta',
24 | 'Environment :: Web Environment',
25 | 'Framework :: Django',
26 | 'Intended Audience :: Developers',
27 | 'License :: OSI Approved :: BSD License',
28 | 'Operating System :: OS Independent',
29 | 'Programming Language :: Python',
30 | 'Topic :: Internet :: WWW/HTTP',
31 | ],
32 | install_requires=[
33 | 'Django',
34 | 'restkit',
35 | 'wsgiref',
36 | 'simplejson',
37 | 'djangorestframework'
38 | ],
39 | tests_require={
40 | 'Piston-tests': ['django-piston'],
41 | }
42 | )
43 |
--------------------------------------------------------------------------------
/django_roa/remoteauth/backends.py:
--------------------------------------------------------------------------------
1 | from django_roa.remoteauth.models import User
2 | from django.contrib.auth.backends import ModelBackend
3 |
4 |
5 | class RemoteUserModelBackend(ModelBackend):
6 | """
7 | Authenticates against django_roa.remoteauth.models.RemoteUser.
8 | """
9 | def authenticate(self, username=None, password=None, **kwargs):
10 | try:
11 | user = User.objects.get(username=username)
12 | if user.check_password(password):
13 | return user
14 | except User.DoesNotExist:
15 | return None
16 |
17 | def get_group_permissions(self, user_obj, obj=None):
18 | """
19 | Returns a set of permission strings that this user has through his/her
20 | groups.
21 | """
22 | if not hasattr(user_obj, '_group_perm_cache'):
23 | # TODO: improve performances
24 | permissions = [u"%s.%s" % (p.content_type.app_label, p.codename) \
25 | for group in user_obj.groups.all() \
26 | for p in group.permissions.all()]
27 | user_obj._group_perm_cache = permissions
28 | return user_obj._group_perm_cache
29 |
30 | def get_user(self, user_id):
31 | try:
32 | return User.objects.get(pk=user_id)
33 | except User.DoesNotExist:
34 | return None
35 |
--------------------------------------------------------------------------------
/examples/twitter_roa/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | from django_roa import Model, Manager
4 | from django_roa.db.query import RemoteQuerySet
5 |
6 | class User(Model):
7 | name = models.CharField(max_length=255)
8 | screen_name = models.CharField(max_length=255)
9 | description = models.TextField()
10 |
11 | def __unicode__(self):
12 | return u'%s (%s)' % (self.name, self.screen_name)
13 |
14 | @staticmethod
15 | def get_resource_url_list():
16 | return u'http://api.twitter.com/1/users/lookup.json?screen_name=twitterapi,twitter,twittersearch,twittermedia,twittermobile'
17 |
18 | def get_resource_url_count(self):
19 | return User.get_resource_url_list()
20 |
21 |
22 | class FakeCountRemoteQuerySet(RemoteQuerySet):
23 | def count(self):
24 | """
25 | Because trying to count the whole number of tweets is stupid.
26 | """
27 | return 20
28 |
29 |
30 | class TweetManager(Manager):
31 | def get_query_set(self):
32 | return FakeCountRemoteQuerySet(self.model)
33 |
34 |
35 | class Tweet(Model):
36 | text = models.TextField()
37 | source = models.CharField(max_length=50)
38 | user = models.ForeignKey(User)
39 |
40 | objects = TweetManager()
41 |
42 | def __unicode__(self):
43 | return u'%s (%s)' % (self.text, self.id)
44 |
45 | @staticmethod
46 | def get_resource_url_list():
47 | return u'http://api.twitter.com/1/statuses/public_timeline.json'
48 |
--------------------------------------------------------------------------------
/django_roa/__init__.py:
--------------------------------------------------------------------------------
1 | # Depends on settings for flexibility
2 | from django.conf import settings
3 | from django.db.models import signals, Model as DjangoModel
4 | from django.db.models.manager import Manager as DjangoManager
5 | from django.contrib.admin.options import ModelAdmin as DjangoModelAdmin
6 | from django.contrib.admin.options import StackedInline as DjangoStackedInline, \
7 | TabularInline as DjangoTabularInline
8 |
9 | from django_roa.db.models import ROAModel
10 | from django_roa.db.managers import ROAManager
11 | from django_roa.db.admin import ROAModelAdmin, ROAStackedInline, ROATabularInline
12 |
13 | ROA_MODELS = getattr(settings, "ROA_MODELS", False)
14 | Model = ROA_MODELS and ROAModel or DjangoModel
15 | Manager = ROA_MODELS and ROAManager or DjangoManager
16 | ModelAdmin = ROA_MODELS and ROAModelAdmin or DjangoModelAdmin
17 | StackedInline = ROA_MODELS and ROAStackedInline or DjangoStackedInline
18 | TabularInline = ROA_MODELS and ROATabularInline or DjangoTabularInline
19 |
20 | def ensure_roa_manager(sender, **kwargs):
21 | cls = sender
22 | manager = getattr(cls, '_default_manager', None)
23 | if (not manager and not cls._meta.abstract) \
24 | or (ROA_MODELS and hasattr(cls, 'get_resource_url_list') \
25 | and not hasattr(manager, 'is_roa_manager')):
26 | # Create the default manager, if needed.
27 | cls.add_to_class('objects', Manager())
28 | cls.add_to_class('_default_manager', Manager())
29 |
30 | signals.class_prepared.connect(ensure_roa_manager)
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009, David Larlet, http://larlet.com
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 | * Redistributions of source code must retain the above copyright
7 | notice, this list of conditions and the following disclaimer.
8 | * Redistributions in binary form must reproduce the above copyright
9 | notice, this list of conditions and the following disclaimer in the
10 | documentation and/or other materials provided with the distribution.
11 | * Neither the name of Welldev, Webrise nor the
12 | names of its contributors may be used to endorse or promote products
13 | derived from this software without specific prior written permission.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS'' AND
16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR
19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/backend/backend/api/views.py:
--------------------------------------------------------------------------------
1 | from .mixins import ModelViewSet
2 | from .models import Account, Reporter, Article, Tag
3 | from .serializers import AccountSerializer, ReporterSerializer, ArticleSerializer, \
4 | TagSerializer, ReversedReporterSerializer, ReversedArticleSerializer, \
5 | ReversedTagSerializer, ReversedAccountSerializer
6 |
7 |
8 | # Filters and permissions are declared in settings
9 |
10 |
11 | class AccountViewSet(ModelViewSet):
12 | queryset = Account.objects.all()
13 | serializer_class = AccountSerializer
14 |
15 |
16 | class ReporterViewSet(ModelViewSet):
17 | queryset = Reporter.objects.all()
18 | serializer_class = ReporterSerializer
19 |
20 |
21 | class ArticleViewSet(ModelViewSet):
22 | queryset = Article.objects.all()
23 | serializer_class = ArticleSerializer
24 |
25 |
26 | class TagViewSet(ModelViewSet):
27 | queryset = Tag.objects.all()
28 | serializer_class = TagSerializer
29 | # Do not paginate the tags, for unpaginated testing
30 | paginate_by = None
31 |
32 |
33 | # Reversed
34 |
35 |
36 | class ReversedTagViewSet(ModelViewSet):
37 | queryset = Tag.objects.all()
38 | serializer_class = ReversedTagSerializer
39 |
40 |
41 | class ReversedArticleViewSet(ModelViewSet):
42 | queryset = Article.objects.all()
43 | serializer_class = ReversedArticleSerializer
44 |
45 |
46 | class ReversedReporterViewSet(ModelViewSet):
47 | queryset = Reporter.objects.all()
48 | serializer_class = ReversedReporterSerializer
49 |
50 |
51 | class ReversedAccountViewSet(ModelViewSet):
52 | queryset = Account.objects.all()
53 | serializer_class = ReversedAccountSerializer
54 |
--------------------------------------------------------------------------------
/examples/django_roa_server/settings.py:
--------------------------------------------------------------------------------
1 | import os
2 | ROOT_PATH = os.path.dirname(__file__)
3 |
4 | TEMPLATE_DEBUG = DEBUG = True
5 | MANAGERS = ADMINS = ()
6 | DATABASE_ENGINE = 'sqlite3'
7 | DATABASE_NAME = os.path.join(ROOT_PATH, 'testdb.sqlite')
8 | DATABASES = {
9 | 'default': {
10 | 'ENGINE': 'django.db.backends.sqlite3',
11 | 'NAME': os.path.join(ROOT_PATH, 'testdb.sqlite'),
12 | }
13 | }
14 |
15 | TIME_ZONE = 'America/Chicago'
16 | LANGUAGE_CODE = 'en-us'
17 | SITE_ID = 1
18 | USE_I18N = True
19 | MEDIA_ROOT = ''
20 | MEDIA_URL = ''
21 | ADMIN_MEDIA_PREFIX = '/media/'
22 | SECRET_KEY = '2+@4vnr#v8e273^+a)g$8%dre^dwcn#d&n#8+l6jk7r#$p&3zk'
23 | TEMPLATE_LOADERS = (
24 | 'django.template.loaders.filesystem.load_template_source',
25 | 'django.template.loaders.app_directories.load_template_source',
26 | )
27 | TEMPLATE_CONTEXT_PROCESSORS = (
28 | "django.core.context_processors.auth",
29 | "django.core.context_processors.debug",
30 | "django.core.context_processors.i18n",
31 | "django.core.context_processors.request",
32 | )
33 | MIDDLEWARE_CLASSES = (
34 | 'django.middleware.common.CommonMiddleware',
35 | 'django.contrib.sessions.middleware.SessionMiddleware',
36 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
37 | )
38 | ROOT_URLCONF = 'urls'
39 | TEMPLATE_DIRS = (os.path.join(ROOT_PATH, 'templates'),)
40 | INSTALLED_APPS = (
41 | 'django_roa',
42 | 'django_roa_server',
43 | 'django.contrib.auth',
44 | 'django.contrib.contenttypes',
45 | 'django.contrib.sessions',
46 | #'django.contrib.sites',
47 | #'django.contrib.admin',
48 | )
49 | SERIALIZATION_MODULES = {
50 | 'custom' : 'examples.django_roa_client.serializers',
51 | }
52 | PISTON_DISPLAY_ERRORS = False
53 |
54 | ## Logging settings
55 | import logging
56 | logging.basicConfig(level=logging.DEBUG, format="%(name)s - %(message)s")
57 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/backend/backend/api/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 | from .models import Account, Reporter, Article, Tag
3 |
4 |
5 | #
6 | # Model serializers
7 | # Typical relationships applications
8 | #
9 |
10 |
11 | class AccountSerializer(serializers.ModelSerializer):
12 | class Meta:
13 | model = Account
14 | fields = ('id', 'email')
15 |
16 |
17 | class ReporterSerializer(serializers.ModelSerializer):
18 | account = AccountSerializer()
19 | class Meta:
20 | model = Reporter
21 | fields = ('id', 'account', 'first_name', 'last_name')
22 |
23 |
24 | class ArticleSerializer(serializers.ModelSerializer):
25 | reporter = ReporterSerializer()
26 | class Meta:
27 | model = Article
28 | fields = ('id', 'headline', 'pub_date', 'reporter')
29 |
30 |
31 | class TagSerializer(serializers.ModelSerializer):
32 | articles = ArticleSerializer(many=True)
33 | class Meta:
34 | model = Tag
35 | fields = ('id', 'label', 'articles')
36 |
37 |
38 | #
39 | # Model serializers
40 | # Reversed relationships applications
41 | #
42 |
43 |
44 | class ReversedTagSerializer(serializers.ModelSerializer):
45 | class Meta:
46 | model = Tag
47 | fields = ('id', 'label')
48 |
49 |
50 | class ReversedArticleSerializer(serializers.ModelSerializer):
51 | tags = ReversedTagSerializer(many=True)
52 | class Meta:
53 | model = Article
54 | fields = ('id', 'headline', 'pub_date', 'tags')
55 |
56 |
57 | class ReversedReporterSerializer(serializers.ModelSerializer):
58 | articles = ReversedArticleSerializer()
59 |
60 | class Meta:
61 | model = Reporter
62 | fields = ('id', 'first_name', 'last_name', 'articles')
63 |
64 |
65 | class ReversedAccountSerializer(serializers.ModelSerializer):
66 | reporter = ReversedReporterSerializer()
67 | class Meta:
68 | model = Account
69 | fields = ('id', 'email', 'reporter')
70 |
71 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/frontend/frontend/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 | from .models import Account, Reporter, Article, Tag
3 |
4 |
5 | #
6 | # Model serializers
7 | # Typical relationships applications
8 | #
9 |
10 |
11 | class AccountSerializer(serializers.ModelSerializer):
12 | class Meta:
13 | model = Account
14 | fields = ('id', 'email')
15 |
16 |
17 | class ReporterSerializer(serializers.ModelSerializer):
18 | account = AccountSerializer()
19 | class Meta:
20 | model = Reporter
21 | fields = ('id', 'account', 'first_name', 'last_name')
22 |
23 |
24 | class ArticleSerializer(serializers.ModelSerializer):
25 | reporter = ReporterSerializer()
26 | class Meta:
27 | model = Article
28 | fields = ('id', 'headline', 'pub_date', 'reporter')
29 |
30 |
31 | class TagSerializer(serializers.ModelSerializer):
32 | articles = ArticleSerializer(many=True)
33 | class Meta:
34 | model = Tag
35 | fields = ('id', 'label', 'articles')
36 |
37 |
38 | #
39 | # Model serializers
40 | # Reversed relationships applications
41 | #
42 |
43 |
44 | class ReversedTagSerializer(serializers.ModelSerializer):
45 | class Meta:
46 | model = Tag
47 | fields = ('id', 'label')
48 |
49 |
50 | class ReversedArticleSerializer(serializers.ModelSerializer):
51 | tags = ReversedTagSerializer(many=True)
52 | class Meta:
53 | model = Article
54 | fields = ('id', 'headline', 'pub_date', 'tags')
55 |
56 |
57 | class ReversedReporterSerializer(serializers.ModelSerializer):
58 | articles = ReversedArticleSerializer()
59 |
60 | class Meta:
61 | model = Reporter
62 | fields = ('id', 'first_name', 'last_name', 'articles')
63 |
64 |
65 | class ReversedAccountSerializer(serializers.ModelSerializer):
66 | reporter = ReversedReporterSerializer()
67 | class Meta:
68 | model = Account
69 | fields = ('id', 'email', 'reporter')
70 |
71 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/backend/backend/api/fixtures/initial_data.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "model": "api.account",
4 | "pk": 1,
5 | "fields": {
6 | "email": "john@example.com"
7 | }
8 | },
9 | {
10 | "model": "api.account",
11 | "pk": 2,
12 | "fields": {
13 | "email": "paul@example.com"
14 | }
15 | },
16 | {
17 | "model": "api.reporter",
18 | "pk": 1,
19 | "fields": {
20 | "first_name": "John",
21 | "last_name": "Smith",
22 | "account": 1
23 | }
24 | },
25 | {
26 | "model": "api.reporter",
27 | "pk": 2,
28 | "fields": {
29 | "first_name": "Paul",
30 | "last_name": "Jones",
31 | "account": 2
32 | }
33 | },
34 | {
35 | "model": "api.article",
36 | "pk": 1,
37 | "fields": {
38 | "headline": "John's first story",
39 | "pub_date": "2013-01-04",
40 | "reporter": 1
41 | }
42 | },
43 | {
44 | "model": "api.article",
45 | "pk": 2,
46 | "fields": {
47 | "headline": "John's second story",
48 | "pub_date": "2013-01-31",
49 | "reporter": 1
50 | }
51 | },
52 | {
53 | "model": "api.article",
54 | "pk": 3,
55 | "fields": {
56 | "headline": "Paul's story",
57 | "pub_date": "2013-02-15",
58 | "reporter": 2
59 | }
60 | },
61 | {
62 | "model": "api.tag",
63 | "pk": 1,
64 | "fields": {
65 | "label": "news",
66 | "articles": [1, 2, 3]
67 | }
68 | },
69 | {
70 | "model": "api.tag",
71 | "pk": 2,
72 | "fields": {
73 | "label": "january",
74 | "articles": [1,2]
75 | }
76 | },
77 | {
78 | "model": "api.tag",
79 | "pk": 3,
80 | "fields": {
81 | "label": "february",
82 | "articles": [3]
83 | }
84 | }
85 | ]
86 |
--------------------------------------------------------------------------------
/templates/admin/index.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/base_site.html" %}
2 | {% load i18n %}
3 |
4 | {% block extrastyle %}{% endblock %}
5 |
6 | {% block coltype %}colMS{% endblock %}
7 |
8 | {% block bodyclass %}dashboard{% endblock %}
9 |
10 | {% block breadcrumbs %}{% endblock %}
11 |
12 | {% block content %}
13 |
14 |
15 | {% if app_list %}
16 | {% for app in app_list %}
17 |
43 | {% endfor %}
44 | {% else %}
45 |
{% trans "You don't have permission to edit anything." %}
46 | {% endif %}
47 |
48 | {% endblock %}
49 |
50 | {% block sidebar %}
51 |
52 |
53 |
{% trans 'Recent Actions' %}
54 |
{% trans 'My Actions' %}
55 |
{% trans "Deactivated with remote access." %}
56 |
57 |
58 | {% endblock %}
59 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/frontend/frontend/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django_roa import Model as ROAModel
3 |
4 |
5 | class CommonROAModel(ROAModel):
6 |
7 | class Meta:
8 | abstract = True
9 |
10 | @classmethod
11 | def get_resource_url_list(cls):
12 | return u'http://127.0.0.1:8000/%s/' % (cls.api_base_name)
13 |
14 | def get_resource_url_count(self):
15 | return self.get_resource_url_list()
16 |
17 |
18 | # Declare backend models
19 |
20 |
21 | class Account(CommonROAModel):
22 | id = models.IntegerField(primary_key=True) # don't forget it !
23 | email = models.CharField(max_length=30)
24 |
25 | api_base_name = 'accounts'
26 |
27 | @classmethod
28 | def serializer(cls):
29 | from .serializers import AccountSerializer
30 | return AccountSerializer
31 |
32 |
33 | class Reporter(CommonROAModel):
34 | id = models.IntegerField(primary_key=True) # don't forget it !
35 | account = models.OneToOneField(Account)
36 | first_name = models.CharField(max_length=30)
37 | last_name = models.CharField(max_length=30)
38 |
39 | api_base_name = 'reporters'
40 |
41 | @classmethod
42 | def serializer(cls):
43 | from .serializers import ReporterSerializer
44 | return ReporterSerializer
45 |
46 |
47 | class Article(CommonROAModel):
48 | id = models.IntegerField(primary_key=True) # don't forget it !
49 | headline = models.CharField(max_length=100)
50 | pub_date = models.DateField()
51 | reporter = models.ForeignKey(Reporter, related_name='articles')
52 |
53 | api_base_name = 'articles'
54 |
55 | @classmethod
56 | def serializer(cls):
57 | from .serializers import ArticleSerializer
58 | return ArticleSerializer
59 |
60 |
61 | class Tag(CommonROAModel):
62 | id = models.IntegerField(primary_key=True) # don't forget it !
63 | label = models.CharField(max_length=30)
64 | articles = models.ManyToManyField(Article, related_name='tags')
65 |
66 | api_base_name = 'tags'
67 |
68 | @classmethod
69 | def serializer(cls):
70 | from .serializers import TagSerializer
71 | return TagSerializer
72 |
--------------------------------------------------------------------------------
/examples/twitter_roa/settings.py:
--------------------------------------------------------------------------------
1 | import os
2 | ROOT_PATH = os.path.dirname(__file__)
3 |
4 | TEMPLATE_DEBUG = DEBUG = True
5 | MANAGERS = ADMINS = ()
6 | DATABASE_ENGINE = 'sqlite3'
7 | DATABASE_NAME = os.path.join(ROOT_PATH, 'testdb.sqlite')
8 |
9 | TIME_ZONE = 'America/Chicago'
10 | LANGUAGE_CODE = 'en-us'
11 | SITE_ID = 1
12 | USE_I18N = True
13 | MEDIA_ROOT = ''
14 | MEDIA_URL = ''
15 | ADMIN_MEDIA_PREFIX = '/media/'
16 | SECRET_KEY = '2+@4vnr#v8e273^+a)g$8%dre^dwcn#d&n#8+l6jk7r#$p&3zk'
17 | TEMPLATE_LOADERS = (
18 | 'django.template.loaders.filesystem.load_template_source',
19 | 'django.template.loaders.app_directories.load_template_source',
20 | )
21 | TEMPLATE_CONTEXT_PROCESSORS = (
22 | "django.core.context_processors.auth",
23 | "django.core.context_processors.debug",
24 | "django.core.context_processors.i18n",
25 | "django.core.context_processors.request",
26 | )
27 | MIDDLEWARE_CLASSES = (
28 | 'django.middleware.common.CommonMiddleware',
29 | 'django.contrib.sessions.middleware.SessionMiddleware',
30 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
31 | )
32 | ROOT_URLCONF = 'urls'
33 | TEMPLATE_DIRS = (os.path.join(ROOT_PATH, '../../templates'),)
34 | INSTALLED_APPS = (
35 | 'django_roa',
36 | 'twitter_roa',
37 | 'django.contrib.admin',
38 | 'django.contrib.auth',
39 | 'django.contrib.contenttypes',
40 | 'django.contrib.sessions',
41 | )
42 | SESSION_ENGINE = "django.contrib.sessions.backends.file"
43 | SERIALIZATION_MODULES = {
44 | 'twitter' : 'examples.twitter_roa.serializers',
45 | }
46 |
47 | ## ROA custom settings
48 | ROA_MODELS = True # set to False if you'd like to develop/test locally
49 | ROA_FORMAT = 'twitter' # json or xml
50 | ROA_DJANGO_ERRORS = True # useful to ease debugging if you use test server
51 |
52 | ROA_URL_OVERRIDES_DETAIL = {
53 | 'twitter_roa.tweet': lambda o: u'http://api.twitter.com/1/statuses/show/%s.json' % o.id,
54 | 'twitter_roa.user': lambda o: u'http://api.twitter.com/1/users/show.json?user_id=%s' % o.id,
55 | }
56 | ROA_ARGS_NAMES_MAPPING = {
57 | 'filter_id__exact': 'user_id',
58 | }
59 | ROA_CUSTOM_ARGS = {
60 | 'include_entities': 'false',
61 | 'skip_status': 'true',
62 | }
63 |
64 | ## Logging settings
65 | import logging
66 | logging.basicConfig(level=logging.DEBUG, format="%(name)s - %(message)s")
67 |
--------------------------------------------------------------------------------
/examples/twitter_roa/serializers.py:
--------------------------------------------------------------------------------
1 | import simplejson
2 | from StringIO import StringIO
3 |
4 | from django.conf import settings
5 | from django.db import models
6 | from django.utils.encoding import smart_unicode
7 | from django.core.serializers import base
8 | from django.core.serializers.json import Serializer as JSONSerializer
9 | from django.core.serializers.python import _get_model
10 |
11 | DEFAULT_CHARSET = getattr(settings, 'DEFAULT_CHARSET', 'utf-8')
12 |
13 | class Serializer(JSONSerializer):
14 | pass
15 |
16 |
17 | def Deserializer(stream_or_string, **options):
18 | """
19 | Deserialize a stream or string of JSON data.
20 | """
21 | if isinstance(stream_or_string, basestring):
22 | stream = StringIO(stream_or_string)
23 | else:
24 | stream = stream_or_string
25 | models.get_apps()
26 | object_list = simplejson.load(stream)
27 | if not isinstance(object_list, list):
28 | object_list = [object_list]
29 | for obj in object_list:
30 | # Look up the model and starting build a dict of data for it.
31 | if 'screen_name' in obj:
32 | Model = _get_model('twitter_roa.user')
33 | else:
34 | Model = _get_model("twitter_roa.tweet")
35 | data = {}
36 | m2m_data = {}
37 |
38 | # Handle each field
39 | for (field_name, field_value) in obj.iteritems():
40 | if isinstance(field_value, str):
41 | field_value = smart_unicode(
42 | field_value,
43 | options.get("encoding", DEFAULT_CHARSET),
44 | strings_only=True)
45 |
46 | try:
47 | field = Model._meta.get_field(field_name)
48 | except models.FieldDoesNotExist:
49 | continue
50 |
51 | # Handle FK fields
52 | if field.rel and isinstance(field.rel, models.ManyToOneRel):
53 | if field_value is not None:
54 | data[field.attname] = field.rel.to._meta.\
55 | get_field(field.rel.field_name).\
56 | to_python(field_value['id'])
57 | else:
58 | data[field.attname] = None
59 |
60 | # Handle all other fields
61 | else:
62 | data[field.name] = field.to_python(field_value)
63 | yield base.DeserializedObject(Model(**data), m2m_data)
64 |
--------------------------------------------------------------------------------
/django_roa/db/exceptions.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.utils.html import strip_tags
3 | from django.utils.text import unescape_entities
4 | from django.utils.encoding import force_unicode
5 |
6 | ROA_DJANGO_ERRORS = getattr(settings, 'ROA_DJANGO_ERRORS', False)
7 |
8 | class ROAException(Exception):
9 | def __init__(self, exception):
10 |
11 | status_int = hasattr(exception, 'status_int')
12 | status_code = hasattr(exception, 'status_code')
13 | if status_code:
14 | self.status_code = exception.status_code
15 | elif status_int:
16 | self.status_code = exception.status_int
17 | else:
18 | self.status_code = "XXX"
19 |
20 | if hasattr(exception, 'message'):
21 | self.msg = force_unicode(exception.message)
22 | else:
23 | self.msg = force_unicode(exception)
24 |
25 | def __str__(self):
26 | if ROA_DJANGO_ERRORS and '' in self.msg:
27 | return self.parse_django_error()
28 | return self.msg
29 |
30 | def parse_django_error(self):
31 | """Extract the summary part of a Django HTML error."""
32 | try:
33 | summary = self.msg.split(u'\n\n ', 1)[1]\
34 | .split(u'
Python Executable: | ', 1)[0]
35 | traceback = self.msg.split(u'\n\nTraceback:', 1)[1]\
36 | .split(u'', 1)[0]
37 | except IndexError:
38 | return self.msg
39 | result = []
40 | title = None
41 | for line in strip_tags(summary).split('\n'):
42 | line_content = unescape_entities(line.strip())
43 | if line_content:
44 | if line_content.endswith(':'):
45 | title = line_content
46 | elif title is None:
47 | title = "%s:" % line_content
48 | else:
49 | result.append("%s %s\n" % (title, line_content))
50 | result.append("Status code: %s" % self.status_code)
51 | indent, indent2 = u' ', u' '
52 | return u"%(summary)s %(traceback)s".strip() % {
53 | 'summary': indent.join(force_unicode(line) for line in result),
54 | 'traceback': indent2.join(force_unicode(line+"\n") \
55 | for line in traceback.split('\n')),
56 | }
57 |
58 |
59 | class ROANotImplementedYetException(Exception):
60 | pass
61 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/frontend/frontend/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for frontend project.
3 |
4 | For more information on this file, see
5 | https://docs.djangoproject.com/en/1.6/topics/settings/
6 |
7 | For the full list of settings and their values, see
8 | https://docs.djangoproject.com/en/1.6/ref/settings/
9 | """
10 |
11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
12 | import os
13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__))
14 |
15 |
16 | # Quick-start development settings - unsuitable for production
17 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/
18 |
19 | # SECURITY WARNING: keep the secret key used in production secret!
20 | SECRET_KEY = 'a+q*2e&q#_w=rivx@ky8+833lx%xwtlz3-)jw@gd2()b(bmf3v'
21 |
22 | # SECURITY WARNING: don't run with debug turned on in production!
23 | DEBUG = True
24 |
25 | TEMPLATE_DEBUG = True
26 |
27 | ALLOWED_HOSTS = []
28 |
29 |
30 | # Application definition
31 |
32 | INSTALLED_APPS = (
33 | 'django.contrib.admin',
34 | 'django.contrib.auth',
35 | 'django.contrib.contenttypes',
36 | 'django.contrib.sessions',
37 | 'django.contrib.messages',
38 | 'django.contrib.staticfiles',
39 | 'django_roa',
40 | 'frontend',
41 | )
42 |
43 | MIDDLEWARE_CLASSES = (
44 | 'django.contrib.sessions.middleware.SessionMiddleware',
45 | 'django.middleware.common.CommonMiddleware',
46 | 'django.middleware.csrf.CsrfViewMiddleware',
47 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
48 | 'django.contrib.messages.middleware.MessageMiddleware',
49 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
50 | )
51 |
52 | ROOT_URLCONF = 'frontend.urls'
53 |
54 | WSGI_APPLICATION = 'frontend.wsgi.application'
55 |
56 |
57 | # Database
58 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases
59 |
60 | DATABASES = {
61 | 'default': {
62 | 'ENGINE': 'django.db.backends.sqlite3',
63 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
64 | }
65 | }
66 |
67 | # Internationalization
68 | # https://docs.djangoproject.com/en/1.6/topics/i18n/
69 |
70 | LANGUAGE_CODE = 'en-us'
71 |
72 | TIME_ZONE = 'UTC'
73 |
74 | USE_I18N = True
75 |
76 | USE_L10N = True
77 |
78 | USE_TZ = True
79 |
80 |
81 | # Static files (CSS, JavaScript, Images)
82 | # https://docs.djangoproject.com/en/1.6/howto/static-files/
83 |
84 | STATIC_URL = '/static/'
85 |
86 |
87 | #
88 | # ROA
89 | #
90 |
91 | ROA_MODELS = True # set to False if you'd like to develop/test locally
92 | ROA_FORMAT = 'json' # json or xml
93 | ROA_DJANGO_ERRORS = True # useful to ease debugging if you use test server
94 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/backend/backend/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for backend project.
3 |
4 | For more information on this file, see
5 | https://docs.djangoproject.com/en/1.6/topics/settings/
6 |
7 | For the full list of settings and their values, see
8 | https://docs.djangoproject.com/en/1.6/ref/settings/
9 | """
10 |
11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
12 | import os
13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__))
14 |
15 |
16 | # Quick-start development settings - unsuitable for production
17 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/
18 |
19 | # SECURITY WARNING: keep the secret key used in production secret!
20 | SECRET_KEY = '^586q83uc(6v0y$z@u(8h2sr&$+qdlw0a3q&+(4=u^5jvu5r#6'
21 |
22 | # SECURITY WARNING: don't run with debug turned on in production!
23 | DEBUG = True
24 |
25 | TEMPLATE_DEBUG = True
26 |
27 | ALLOWED_HOSTS = []
28 |
29 |
30 | # Application definition
31 |
32 | INSTALLED_APPS = (
33 | 'django.contrib.admin',
34 | 'django.contrib.auth',
35 | 'django.contrib.contenttypes',
36 | 'django.contrib.sessions',
37 | 'django.contrib.messages',
38 | 'django.contrib.staticfiles',
39 | 'rest_framework',
40 | 'backend.api',
41 | )
42 |
43 | MIDDLEWARE_CLASSES = (
44 | 'django.contrib.sessions.middleware.SessionMiddleware',
45 | 'django.middleware.common.CommonMiddleware',
46 | 'django.middleware.csrf.CsrfViewMiddleware',
47 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
48 | 'django.contrib.messages.middleware.MessageMiddleware',
49 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
50 | )
51 |
52 | ROOT_URLCONF = 'backend.urls'
53 |
54 | WSGI_APPLICATION = 'backend.wsgi.application'
55 |
56 |
57 | # Database
58 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases
59 |
60 | DATABASES = {
61 | 'default': {
62 | 'ENGINE': 'django.db.backends.sqlite3',
63 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
64 | }
65 | }
66 |
67 | # Internationalization
68 | # https://docs.djangoproject.com/en/1.6/topics/i18n/
69 |
70 | LANGUAGE_CODE = 'en-us'
71 |
72 | TIME_ZONE = 'UTC'
73 |
74 | USE_I18N = True
75 |
76 | USE_L10N = True
77 |
78 | USE_TZ = True
79 |
80 |
81 | # Static files (CSS, JavaScript, Images)
82 | # https://docs.djangoproject.com/en/1.6/howto/static-files/
83 |
84 | STATIC_URL = '/static/'
85 |
86 | #
87 | # DRF:
88 | #
89 |
90 | REST_FRAMEWORK = {
91 | # 'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',),
92 | 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.AllowAny',),
93 | 'PAGINATE_BY': 20
94 | }
95 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/backend/backend/api/mixins.py:
--------------------------------------------------------------------------------
1 | from rest_framework import viewsets
2 | from django.db import models
3 | from functools import reduce
4 | import operator
5 |
6 |
7 | class FilterByKeyMixin(object):
8 | """
9 | Custom viewset: add filter_key feature
10 | """
11 | search_param = 'search'
12 | order_param = 'order_by'
13 | filter_param_prefix = 'filter_'
14 |
15 | def construct_search(self, field_name):
16 | if field_name.startswith('^'):
17 | return "%s__istartswith" % field_name[1:]
18 | elif field_name.startswith('='):
19 | return "%s__iexact" % field_name[1:]
20 | elif field_name.startswith('@'):
21 | return "%s__search" % field_name[1:]
22 | else:
23 | return "%s__icontains" % field_name
24 |
25 | def get_search_terms(self, request):
26 | """
27 | Search terms are set by a ?search=... query parameter,
28 | and may be comma and/or whitespace delimited.
29 | """
30 | params = request.QUERY_PARAMS.get(self.search_param, '')
31 | return params.replace(',', ' ').split()
32 |
33 | def filter_queryset(self, queryset):
34 | """
35 | Parses QUERY_PARAMS and apply them
36 | """
37 |
38 | for param in self.request.QUERY_PARAMS.keys():
39 | # filter ?
40 | if param.startswith(self.filter_param_prefix):
41 | key_ = param.split(self.filter_param_prefix)[1]
42 | value_ = self.request.QUERY_PARAMS[param]
43 | if value_ is not None:
44 | queryset = queryset.filter(**{'%s' % (key_): value_})
45 |
46 | # order by ?
47 | elif param == self.order_param:
48 | value_ = self.request.QUERY_PARAMS[param]
49 | if value_ is not None:
50 | queryset = queryset.order_by(value_)
51 |
52 | # search ?
53 | elif param == self.search_param:
54 | search_fields = getattr(self, 'search_fields', None)
55 | if search_fields:
56 | orm_lookups = [self.construct_search(str(search_field)) for search_field in search_fields]
57 |
58 | for search_term in self.get_search_terms(self.request):
59 | or_queries = [models.Q(**{orm_lookup: search_term}) for orm_lookup in orm_lookups]
60 | queryset = queryset.filter(reduce(operator.or_, or_queries))
61 |
62 | limit_start = self.request.QUERY_PARAMS.get('limit_start', None)
63 | limit_stop = self.request.QUERY_PARAMS.get('limit_stop', None)
64 |
65 | if limit_stop is not None:
66 | limit_start = int(limit_start) if limit_start is not None else 0
67 | limit_stop = int(limit_stop)
68 | queryset = queryset[limit_start:limit_stop]
69 |
70 | elif limit_start is not None:
71 | limit_start = int(limit_start)
72 | queryset = queryset[limit_start:]
73 |
74 | return queryset
75 |
76 |
77 | class ModelViewSet(FilterByKeyMixin, viewsets.ModelViewSet):
78 | pass
79 |
--------------------------------------------------------------------------------
/examples/django_roa_client/settings.py:
--------------------------------------------------------------------------------
1 | import os
2 | ROOT_PATH = os.path.dirname(__file__)
3 |
4 | TEMPLATE_DEBUG = DEBUG = True
5 | MANAGERS = ADMINS = ()
6 |
7 | DATABASE_ENGINE = 'sqlite3'
8 | DATABASE_NAME = os.path.join(ROOT_PATH, 'testdb.sqlite')
9 | DATABASES = {
10 | 'default': {
11 | 'ENGINE': 'django.db.backends.sqlite3',
12 | 'NAME': os.path.join(ROOT_PATH, 'testdb.sqlite'),
13 | }
14 | }
15 |
16 | TIME_ZONE = 'America/Chicago'
17 | LANGUAGE_CODE = 'en-us'
18 | SITE_ID = 1
19 | USE_I18N = True
20 | MEDIA_ROOT = ''
21 | MEDIA_URL = ''
22 | ADMIN_MEDIA_PREFIX = '/media/'
23 | SECRET_KEY = '2+@4vnr#v8e273^+a)g$8%dre^dwcn#d&n#8+l6jk7r#$p&3zk'
24 | TEMPLATE_LOADERS = (
25 | 'django.template.loaders.filesystem.load_template_source',
26 | 'django.template.loaders.app_directories.load_template_source',
27 | )
28 | TEMPLATE_CONTEXT_PROCESSORS = (
29 | "django.core.context_processors.auth",
30 | "django.core.context_processors.debug",
31 | "django.core.context_processors.i18n",
32 | "django.core.context_processors.request",
33 | )
34 | MIDDLEWARE_CLASSES = (
35 | 'django.middleware.common.CommonMiddleware',
36 | 'django.contrib.sessions.middleware.SessionMiddleware',
37 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
38 | )
39 | ROOT_URLCONF = 'urls'
40 | TEMPLATE_DIRS = (os.path.join(ROOT_PATH, '../../templates'),)
41 | INSTALLED_APPS = (
42 | 'django_roa',
43 | 'django_roa.remoteauth',
44 | 'django_roa_client',
45 | 'django.contrib.auth',
46 | 'django.contrib.admin',
47 | 'django.contrib.contenttypes',
48 | 'django.contrib.sessions',
49 | )
50 | AUTHENTICATION_BACKENDS = (
51 | 'django_roa.remoteauth.backends.RemoteUserModelBackend',
52 | )
53 | SESSION_ENGINE = "django.contrib.sessions.backends.file"
54 | SERIALIZATION_MODULES = {
55 | 'django': 'examples.django_roa_client.serializers',
56 | }
57 |
58 | ## ROA custom settings
59 | ROA_MODELS = True # set to False if you'd like to develop/test locally
60 | ROA_FORMAT = 'django' # json or xml
61 | # specify the headers sent to the ws from restkit
62 | ROA_HEADERS = {
63 | 'Content-Type': 'application/x-www-form-urlencoded',
64 | }
65 | ROA_DJANGO_ERRORS = True # useful to ease debugging if you use test server
66 |
67 | ROA_URL_OVERRIDES_LIST = {
68 | 'django_roa_client.remotepagewithoverriddenurls': u'http://127.0.0.1:8081/django_roa_server/remotepagewithoverriddenurls/',
69 | }
70 | ROA_URL_OVERRIDES_DETAIL = {
71 | 'django_roa_client.remotepagewithoverriddenurls': lambda o: u"%s%s-%s/" % (o.get_resource_url_list(), o.id, o.slug),
72 | }
73 | ROA_MODEL_NAME_MAPPING = (
74 | # local name: remote name
75 | ('django_roa_client.', 'django_roa_server.'),
76 | ('remoteauth.', 'auth.'),
77 | )
78 | ROA_ARGS_NAMES_MAPPING = {
79 | 'ORDER_BY': 'order',
80 | }
81 | ROA_MODEL_CREATE_MAPPING = {
82 | 'package.somemodel': ['attribute1', 'attribute2', 'attribute3', 'attribute4'],
83 | }
84 | ROA_MODEL_UPDATE_MAPPING = {
85 | 'package.somemodel': ['attribute2', 'attribute3'],
86 | }
87 | # Enable HTTP authentication through django-piston
88 | from restkit import BasicAuth
89 | ROA_FILTERS = [ BasicAuth('django-roa', 'roa'), ]
90 | # Disable authentication through django-piston
91 | #ROA_FILTERS = []
92 |
93 | ## Logging settings
94 | import logging
95 | logging.basicConfig(level=logging.DEBUG, format="%(name)s - %(message)s")
96 |
--------------------------------------------------------------------------------
/examples/django_roa_server/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | class RemotePage(models.Model):
4 | title = models.CharField(max_length=50, blank=True, null=True)
5 |
6 | def __unicode__(self):
7 | return u'%s (%s)' % (self.title, self.pk)
8 |
9 |
10 | class RemotePageWithManyFields(models.Model):
11 | #auto_field = models.AutoField(primary_key=True)
12 | char_field = models.CharField(max_length=50, blank=True, null=True)
13 | date_field = models.DateField(blank=True, null=True)
14 | datetime_field = models.DateTimeField(blank=True, null=True)
15 | decimal_field = models.DecimalField(decimal_places=3, max_digits=5, blank=True, null=True)
16 | email_field = models.EmailField(blank=True, null=True)
17 | filepath_field = models.FilePathField(blank=True, null=True)
18 | float_field = models.FloatField(blank=True, null=True)
19 | integer_field = models.IntegerField(blank=True, null=True)
20 | ipaddress_field = models.IPAddressField(blank=True, null=True)
21 | positiveinteger_field = models.PositiveIntegerField(blank=True, null=True)
22 | positivesmallinteger_field = models.PositiveSmallIntegerField(blank=True, null=True)
23 | slug_field = models.SlugField(blank=True, null=True)
24 | smallinteger_field = models.SmallIntegerField(blank=True, null=True)
25 | text_field = models.TextField(blank=True, null=True)
26 | time_field = models.TimeField(blank=True, null=True)
27 | url_field = models.URLField(blank=True, null=True)
28 | xml_field = models.TextField(blank=True, null=True)
29 |
30 | file_field = models.FileField(upload_to="files", blank=True, null=True)
31 | image_field = models.ImageField(upload_to="images", blank=True, null=True)
32 |
33 | def __unicode__(self):
34 | return u'%s (%s)' % (self.__class__.__name__, self.pk)
35 |
36 |
37 | class RemotePageWithBooleanFields(models.Model):
38 | boolean_field = models.BooleanField()
39 | null_boolean_field = models.NullBooleanField()
40 |
41 |
42 | class RemotePageWithCustomSlug(models.Model):
43 | title = models.CharField(max_length=50)
44 | slug = models.SlugField()
45 |
46 | def __unicode__(self):
47 | return u'%s (%s)' % (self.title, self.pk)
48 |
49 |
50 | class RemotePageWithOverriddenUrls(models.Model):
51 | title = models.CharField(max_length=50)
52 | slug = models.SlugField()
53 |
54 | def __unicode__(self):
55 | return u'%s (%s)' % (self.title, self.pk)
56 |
57 |
58 | class RemotePageWithRelationsThrough(models.Model):
59 | title = models.CharField(max_length=50)
60 | remote_page_with_relations = models.ForeignKey("RemotePageWithRelations", blank=True, null=True)
61 | remote_page_with_many_fields = models.ForeignKey("RemotePageWithManyFields", blank=True, null=True)
62 |
63 | def __unicode__(self):
64 | return u'%s (%s)' % (self.title, self.pk)
65 |
66 |
67 | class RemotePageWithRelations(models.Model):
68 | title = models.CharField(max_length=50)
69 | remote_page = models.ForeignKey(RemotePage, blank=True, null=True)
70 | remote_page_fields = models.ManyToManyField(RemotePageWithManyFields, through=RemotePageWithRelationsThrough, blank=True, null=True)
71 |
72 | def __unicode__(self):
73 | return u'%s (%s)' % (self.title, self.pk)
74 |
75 |
76 | class RemotePageWithNamedRelations(models.Model):
77 | title = models.CharField(max_length=50)
78 | first_page = models.ForeignKey(RemotePage, blank=True, null=True, related_name="from_first")
79 | last_page = models.ForeignKey(RemotePage, blank=True, null=True, related_name="from_last")
80 |
81 | def __unicode__(self):
82 | return u'%s (%s)' % (self.title, self.pk)
83 |
84 |
85 | class RemotePageWithCustomPrimaryKey(models.Model):
86 | auto_field = models.AutoField(primary_key=True)
87 | title = models.CharField(max_length=50)
88 |
89 | def __unicode__(self):
90 | return u'%s (%s)' % (self.title, self.pk)
91 |
--------------------------------------------------------------------------------
/django_roa/remoteauth/models.py:
--------------------------------------------------------------------------------
1 | import re
2 | from django.utils import timezone
3 | from django.core import validators
4 | from django.contrib.contenttypes.models import ContentType
5 | from django.contrib.auth.models import Permission as DjangoPermission, \
6 | User as DjangoUser, Group as DjangoGroup, \
7 | UserManager as DjangoUserManager
8 | try:
9 | from django.contrib.auth.models import Message as DjangoMessage
10 | except ImportError:
11 | DjangoMessage = None
12 | from django.utils.translation import ugettext_lazy as _
13 | from django.db import models
14 |
15 | from django_roa import Model, Manager
16 |
17 |
18 | class Permission(Model, DjangoPermission):
19 | """
20 | Inherits methods from Django's Permission model.
21 | """
22 | name = models.CharField(_('name'), max_length=50)
23 | # non-standard related_name added to avoid clashes
24 | content_type = models.ForeignKey(ContentType, related_name="permissions")
25 | codename = models.CharField(_('codename'), max_length=100)
26 |
27 | @staticmethod
28 | def get_resource_url_list():
29 | return u'http://127.0.0.1:8081/auth/permission/'
30 |
31 | @classmethod
32 | def serializer(cls):
33 | from .serializers import PermissionSerializer
34 | return PermissionSerializer
35 |
36 |
37 | class Group(Model, DjangoGroup):
38 | """
39 | Inherits methods from Django's Group model.
40 | """
41 | id = models.IntegerField(primary_key=True)
42 | name = models.CharField(_('name'), max_length=80, unique=True)
43 | permissions = models.ManyToManyField(Permission,
44 | verbose_name=_('permissions'), blank=True)
45 |
46 | @staticmethod
47 | def get_resource_url_list():
48 | return u'http://127.0.0.1:8081/auth/group/'
49 |
50 | @classmethod
51 | def serializer(cls):
52 | from .serializers import GroupSerializer
53 | return GroupSerializer
54 |
55 |
56 | class UserManager(Manager, DjangoUserManager):
57 | """
58 | Inherits methods from Django's UserManager manager.
59 | """
60 |
61 |
62 | class User(Model, DjangoUser):
63 | """
64 | Inherits methods from Django's User model.
65 | """
66 | id = models.IntegerField(primary_key=True)
67 | username = models.CharField(_('username'), max_length=30, unique=True,
68 | help_text=_('Required. 30 characters or fewer. Letters, numbers and '
69 | '@/./+/-/_ characters'),
70 | validators=[
71 | validators.RegexValidator(re.compile('^[\w.@+-]+$'), _('Enter a valid username.'), 'invalid')
72 | ])
73 | first_name = models.CharField(_('first name'), max_length=30, blank=True)
74 | last_name = models.CharField(_('last name'), max_length=30, blank=True)
75 | email = models.EmailField(_('email address'), blank=True)
76 | is_staff = models.BooleanField(_('staff status'), default=False,
77 | help_text=_('Designates whether the user can log into this admin '
78 | 'site.'))
79 | is_active = models.BooleanField(_('active'), default=True,
80 | help_text=_('Designates whether this user should be treated as '
81 | 'active. Unselect this instead of deleting accounts.'))
82 | date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
83 |
84 | password = models.CharField(_('password'), max_length=128)
85 | last_login = models.DateTimeField(_('last login'), default=timezone.now)
86 |
87 | is_superuser = models.BooleanField(_('superuser status'), default=False,
88 | help_text=_('Designates that this user has all permissions without '
89 | 'explicitly assigning them.'))
90 | groups = models.ManyToManyField(Group, verbose_name=_('groups'),
91 | blank=True, help_text=_('The groups this user belongs to. A user will '
92 | 'get all permissions granted to each of '
93 | 'his/her group.'),
94 | related_name="user_set", related_query_name="user")
95 | user_permissions = models.ManyToManyField(Permission,
96 | verbose_name=_('user permissions'), blank=True,
97 | help_text=_('Specific permissions for this user.'),
98 | related_name="user_set", related_query_name="user")
99 |
100 | objects = UserManager()
101 |
102 | @staticmethod
103 | def get_resource_url_list():
104 | return u'http://127.0.0.1:8081/auth/user/'
105 |
106 | @classmethod
107 | def serializer(cls):
108 | from .serializers import UserSerializer
109 | return UserSerializer
110 |
111 |
112 | Message = None
113 |
114 | if DjangoMessage:
115 | class Message(Model, DjangoMessage):
116 | """
117 | Inherits methods from Django's Message model.
118 | """
119 | user = models.ForeignKey(User)
120 | message = models.TextField(_('message'))
121 |
122 | @staticmethod
123 | def get_resource_url_list():
124 | return u'http://127.0.0.1:8081/auth/message/'
125 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ===========================================
2 | Django-ROA (Resource Oriented Architecture)
3 | ===========================================
4 |
5 | **Use Django's ORM to model remote API resources.**
6 |
7 | Fork of original `David Larlet Django ROA lib
`_.
8 | Now ROA works directly with an API like `Django Rest Framework `_
9 |
10 | **How does it works:**
11 | Each time a request is passed to the database, the request is intercepted and transformed to an HTTP request to the remote server with the right
12 | method (GET, POST, PUT or DELETE) given the get_resource_url_* methods specified in the model's definition.
13 |
14 |
15 | Documentation
16 | =============
17 |
18 | Initial documentation:
19 |
20 | - `Wiki home `_
21 | - `Getting started with Django-ROA `_
22 | - `Developing with Django-ROA `_
23 |
24 |
25 | Supported versions
26 | ==================
27 |
28 | .. image:: https://travis-ci.org/charles-vdulac/django-roa.png?branch=master
29 | :target: https://travis-ci.org/charles-vdulac/django-roa
30 | :alt: Build Status
31 |
32 | - Django 1.4, 1.5, 1.6, 1.7
33 | - Python 2.6, 2.7
34 |
35 |
36 | Installation
37 | ============
38 |
39 | .. code:: bash
40 |
41 | $ pip install -e git+https://github.com/charles-vdulac/django-roa.git@master#egg=django_roa
42 |
43 |
44 | Fork getting started
45 | ====================
46 |
47 | If you have an API output like this (typical DRF output):
48 |
49 | .. code:: python
50 |
51 | # GET http://api.example.com/articles/
52 | # HTTP 200 OK
53 | # Content-Type: application/json
54 | # Vary: Accept
55 | # Allow: GET, POST, HEAD, OPTIONS
56 |
57 | {
58 | "count": 3,
59 | "next": null,
60 | "previous": null,
61 | "results": [
62 | {
63 | "id": 1,
64 | "headline": "John's first story",
65 | "pub_date": "2013-01-04",
66 | "reporter": {
67 | "id": 1,
68 | "account": {
69 | "id": 1,
70 | "email": "john@example.com"
71 | },
72 | "first_name": "John",
73 | "last_name": "Smith"
74 | }
75 | },
76 | ...
77 | ]
78 | }
79 |
80 | Your code will look like this:
81 |
82 | .. code:: python
83 |
84 | from django.db import models
85 | from django_roa import Model as ROAModel
86 |
87 | class Article(ROAModel):
88 | id = models.IntegerField(primary_key=True) # don't forget it !
89 | headline = models.CharField(max_length=100)
90 | pub_date = models.DateField()
91 | reporter = models.ForeignKey(Reporter, related_name='articles')
92 |
93 | api_base_name = 'articles'
94 |
95 | @classmethod
96 | def serializer(cls):
97 | from .serializers import ArticleSerializer
98 | return ArticleSerializer
99 |
100 | @classmethod
101 | def get_resource_url_list(cls):
102 | return u'http://api.example.com/{base_name}/'.format(
103 | base_name=cls.api_base_name,
104 | )
105 |
106 | def get_resource_url_count(self):
107 | return self.get_resource_url_list()
108 |
109 | .. code:: python
110 |
111 | from rest_framework import serializers
112 | from .models import Article
113 |
114 | class ArticleSerializer(serializers.ModelSerializer):
115 | reporter = ReporterSerializer()
116 | class Meta:
117 | model = Article
118 | fields = ('id', 'headline', 'pub_date', 'reporter')
119 |
120 | Refer to `tests `_ for full example.
121 |
122 | Running tests
123 | =============
124 |
125 | - Initial tests: read `documentation `_
126 | - Fork tests: read `README `_
127 |
128 |
129 | Caveats
130 | =======
131 |
132 | For the moment, the library doesn't work in this case:
133 |
134 | One to one (reversed)
135 | ---------------------
136 |
137 | .. code:: python
138 |
139 | class Reporter(CommonROAModel):
140 | account = models.OneToOneField(Account)
141 | ...
142 |
143 | with fixtures:
144 |
145 | .. code:: json
146 |
147 | {
148 | "model": "api.reporter",
149 | "pk": 1,
150 | "fields": {
151 | "first_name": "John",
152 | "last_name": "Smith",
153 | "account": 1
154 | }
155 | },
156 | {
157 | "model": "api.account",
158 | "pk": 1,
159 | "fields": {
160 | "email": "john@example.com"
161 | }
162 | },
163 |
164 | This works:
165 |
166 | .. code:: python
167 |
168 | reporter = Reporter.objects.get(id=1)
169 | assertEqual(reporter.account.id, 1)
170 | assertEqual(reporter.account.email, 'john@example.com')
171 |
172 | But not this way:
173 |
174 | .. code:: python
175 |
176 | account = Account.objects.get(id=1)
177 | assertEqual(account.reporter.id, 1)
178 | assertEqual(account.reporter.first_name, "John")
179 |
180 |
181 | HTTPS certificate pinning
182 | =========================
183 |
184 | You can pass ssl args (see `ssl.wrap_socket()`) via the `ROA_SSL_ARGS` of your
185 | ``settings.py``.
186 |
187 |
188 | To pin the server certificate, save the public certificate(s) you want to
189 | pin in *pinned-ca.pem* and add the following to your *settings.py* :
190 |
191 | .. code:: python
192 |
193 | from os.path import dirname, join
194 | ROA_SSL_ARGS = {
195 | 'ca_certs': join(dirname(dirname(__file__)), 'pinned-ca.pem'),
196 | 'cert_reqs': True
197 | }
198 |
--------------------------------------------------------------------------------
/examples/django_roa_server/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import url, patterns
2 |
3 | from piston.resource import Resource
4 | from piston.authentication import HttpBasicAuthentication
5 |
6 | from django_roa_server.handlers import RemotePageHandler, \
7 | RemotePageWithManyFieldsHandler, RemotePageWithBooleanFieldsHandler, \
8 | RemotePageWithCustomSlugHandler, RemotePageWithOverriddenUrlsHandler, \
9 | RemotePageWithRelationsHandler, UserHandler, MessageHandler, \
10 | PermissionHandler, GroupHandler, RemotePageCountHandler, \
11 | RemotePageWithManyFieldsCountHandler, RemotePageWithBooleanFieldsCountHandler, \
12 | RemotePageWithCustomSlugCountHandler, RemotePageWithOverriddenUrlsCountHandler, \
13 | RemotePageWithRelationsHandler, RemotePageWithNamedRelationsHandler, \
14 | RemotePageWithNamedRelationsCountHandler, RemotePageWithRelationsThroughHandler, \
15 | RemotePageWithCustomPrimaryKeyHandler, RemotePageWithCustomPrimaryKeyCountHandler, \
16 | RemotePageWithCustomPrimaryKeyCount2Handler
17 |
18 | # Enable HTTP authentication through django-piston
19 | ad = { 'authentication': HttpBasicAuthentication(
20 | realm="django-roa-server",
21 | auth_func = lambda username, password: username == 'django-roa' and password == 'roa'
22 | )}
23 | # Disable authentication through django-piston
24 | #ad = { 'authentication': None}
25 |
26 | remote_pages = Resource(handler=RemotePageHandler, **ad)
27 | remote_pages_count = Resource(handler=RemotePageCountHandler, **ad)
28 |
29 | remote_pages_with_many_fields = Resource(handler=RemotePageWithManyFieldsHandler, **ad)
30 | remote_pages_with_many_fields_count = Resource(handler=RemotePageWithManyFieldsCountHandler, **ad)
31 |
32 | remote_pages_with_boolean_fields = Resource(handler=RemotePageWithBooleanFieldsHandler, **ad)
33 | remote_pages_with_boolean_fields_count = Resource(handler=RemotePageWithBooleanFieldsCountHandler, **ad)
34 |
35 | remote_pages_with_custom_slug = Resource(handler=RemotePageWithCustomSlugHandler, **ad)
36 | remote_pages_with_custom_slug_count = Resource(handler=RemotePageWithCustomSlugCountHandler, **ad)
37 |
38 | remote_pages_with_custom_primary_key = Resource(handler=RemotePageWithCustomPrimaryKeyHandler, **ad)
39 | remote_pages_with_custom_primary_key_count = Resource(handler=RemotePageWithCustomPrimaryKeyCountHandler, **ad)
40 | remote_pages_with_custom_primary_key_count2 = Resource(handler=RemotePageWithCustomPrimaryKeyCount2Handler, **ad)
41 |
42 | remote_pages_with_overridden_urls = Resource(handler=RemotePageWithOverriddenUrlsHandler, **ad)
43 | remote_pages_with_overridden_urls_count = Resource(handler=RemotePageWithOverriddenUrlsCountHandler, **ad)
44 |
45 | remote_pages_with_relations = Resource(handler=RemotePageWithRelationsHandler, **ad)
46 | remote_pages_with_relations_count = Resource(handler=RemotePageWithRelationsHandler, **ad)
47 |
48 | remote_pages_with_relations_through = Resource(handler=RemotePageWithRelationsThroughHandler, **ad)
49 | remote_pages_with_relations_through_count = Resource(handler=RemotePageWithRelationsThroughHandler, **ad)
50 |
51 | remote_pages_with_named_relations = Resource(handler=RemotePageWithNamedRelationsHandler, **ad)
52 | remote_pages_with_named_relations_count = Resource(handler=RemotePageWithNamedRelationsCountHandler, **ad)
53 |
54 | users = Resource(handler=UserHandler, **ad)
55 | messages = None
56 | if MessageHandler:
57 | messages = Resource(handler=MessageHandler, **ad)
58 | permissions = Resource(handler=PermissionHandler, **ad)
59 | groups = Resource(handler=GroupHandler, **ad)
60 |
61 | urlpatterns = patterns('',
62 | # Remote pages counts
63 | url(r'^django_roa_server/remotepage/count/$', remote_pages_count),
64 | url(r'^django_roa_server/remotepagewithmanyfields/count/$', remote_pages_with_many_fields_count),
65 | url(r'^django_roa_server/remotepagewithbooleanfields/count/$', remote_pages_with_boolean_fields_count),
66 | url(r'^django_roa_server/remotepagewithcustomslug/count/$', remote_pages_with_custom_slug_count),
67 | url(r'^django_roa_server/remotepagewithcustomprimarykey/count/$', remote_pages_with_custom_primary_key_count),
68 | url(r'^django_roa_server/remotepagewithcustomprimarykey/count2/$', remote_pages_with_custom_primary_key_count2),
69 | url(r'^django_roa_server/remotepagewithoverriddenurls/count/$', remote_pages_with_overridden_urls_count),
70 | url(r'^django_roa_server/remotepagewithrelations/count/$', remote_pages_with_relations_count),
71 | url(r'^django_roa_server/remotepagewithrelationsthrough/count/$', remote_pages_with_relations_through_count),
72 | url(r'^django_roa_server/remotepagewithnamedrelations/count/$', remote_pages_with_named_relations_count),
73 | url(r'^django_roa_server/remotepagewithproxy/count/$', remote_pages_count),
74 |
75 | # Remote pages
76 | url(r'^django_roa_server/remotepage/?(?P\d+)?/?$', remote_pages),
77 | url(r'^django_roa_server/remotepagewithmanyfields/?(?P\d+)?/?$', remote_pages_with_many_fields),
78 | url(r'^django_roa_server/remotepagewithbooleanfields/?(?P\d+)?/?$', remote_pages_with_boolean_fields),
79 | url(r'^django_roa_server/remotepagewithcustomslug/?(?P[-\w]+)?/?$', remote_pages_with_custom_slug),
80 | url(r'^django_roa_server/remotepagewithcustomprimarykey/?(?P[-\w]+)?/?$', remote_pages_with_custom_primary_key),
81 | url(r'^django_roa_server/remotepagewithoverriddenurls/?(?P[-\w]+)?/?$', remote_pages_with_overridden_urls),
82 | url(r'^django_roa_server/remotepagewithrelations/?(?P\d+)?/?$', remote_pages_with_relations),
83 | url(r'^django_roa_server/remotepagewithrelationsthrough/?(?P\d+)?/?$', remote_pages_with_relations_through),
84 | url(r'^django_roa_server/remotepagewithnamedrelations/?(?P\d+)?/?$', remote_pages_with_named_relations),
85 | url(r'^django_roa_server/remotepagewithproxy/?(?P\d+)?/?$', remote_pages),
86 |
87 | # Auth application
88 | url(r'^auth/user/?(?P\d+)?/?$', users),
89 | url(r'^auth/message/?(?P\d+)?/?$', messages),
90 | url(r'^auth/permission/?(?P\d+)?/?$', permissions),
91 | url(r'^auth/group/?(?P\d+)?/?$', groups),
92 | )
93 |
--------------------------------------------------------------------------------
/examples/django_rest_framework/frontend/frontend/tests.py:
--------------------------------------------------------------------------------
1 | from django.utils.timezone import now
2 | from rest_framework.test import APITestCase
3 | from django_roa.db.exceptions import ROAException
4 | from .models import Account, Article, Tag, Reporter
5 |
6 |
7 | class ROATestCase(APITestCase):
8 |
9 | def test_all(self):
10 |
11 | #
12 | # Get
13 | #
14 |
15 | account = Account.objects.get(id=1)
16 | self.assertEqual(account.id, 1)
17 | self.assertEqual(account.email, 'john@example.com')
18 |
19 | account = Account.objects.get(email='paul@example.com')
20 | self.assertEqual(account.id, 2)
21 | self.assertEqual(account.email, 'paul@example.com')
22 |
23 | # All
24 | accounts = Account.objects.all()
25 | self.assertEqual(accounts.count(), 2)
26 | self.assertEqual(accounts[0].id, 1)
27 | self.assertEqual(accounts[0].email, 'john@example.com')
28 | self.assertEqual(accounts[1].id, 2)
29 | self.assertEqual(accounts[1].email, 'paul@example.com')
30 |
31 | # filter
32 | accounts = Account.objects.filter(email='paul@example.com')
33 | self.assertEqual(accounts.count(), 1)
34 | self.assertEqual(accounts[0].id, 2)
35 | self.assertEqual(accounts[0].email, 'paul@example.com')
36 |
37 | #
38 | # Relationships
39 | # Typical relationships applications
40 | #
41 |
42 | # One to One
43 | reporter = Reporter.objects.get(id=1)
44 | self.assertEqual(reporter.first_name, "John")
45 | self.assertEqual(reporter.account.id, 1)
46 | self.assertEqual(reporter.account.email, 'john@example.com')
47 |
48 | # One to one reversed:
49 | # TODO: doesn't work
50 | # account = Account.objects.get(id=1)
51 | # self.assertEqual(account.reporter.id, 1)
52 | # self.assertEqual(account.reporter.first_name, "John")
53 |
54 | # One to Many
55 | articles = reporter.articles.all()
56 | self.assertEqual(articles.count(), 2)
57 | self.assertEqual(articles[0].id, 1)
58 | self.assertEqual(articles[0].headline, "John's first story")
59 | self.assertEqual(articles[1].id, 2)
60 | self.assertEqual(articles[1].headline, "John's second story")
61 |
62 | # Many to one
63 | article = Article.objects.get(id=1)
64 | self.assertEqual(article.reporter.id, 1)
65 | self.assertEqual(article.reporter.first_name, "John")
66 |
67 | # Many to Many
68 | tags = articles[0].tags.all()
69 | self.assertEqual(tags.count(), 2)
70 | self.assertEqual(tags[0].id, 1)
71 | self.assertEqual(tags[0].label, 'news')
72 | self.assertEqual(tags[1].id, 2)
73 | self.assertEqual(tags[1].label, 'january')
74 |
75 | # Many to Many reversed
76 | tag = Tag.objects.get(id=1)
77 | self.assertEqual(tag.label, 'news')
78 | articles = tag.articles.all()
79 | self.assertEqual(articles.count(), 3)
80 | self.assertEqual(articles[0].id, 1)
81 | self.assertEqual(articles[0].headline, "John's first story")
82 | self.assertEqual(articles[1].id, 2)
83 | self.assertEqual(articles[1].headline, "John's second story")
84 | self.assertEqual(articles[2].id, 3)
85 | self.assertEqual(articles[2].headline, "Paul's story")
86 |
87 | #
88 | # Create
89 | #
90 |
91 | new_account = Account()
92 | new_account.email = 'peter@example.com'
93 | new_account.save()
94 |
95 | self.assertIsNotNone(new_account.id)
96 |
97 | accounts = Account.objects.all()
98 | self.assertEqual(accounts.count(), 3)
99 | self.assertEqual(accounts[0].email, 'john@example.com')
100 | self.assertEqual(accounts[1].email, 'paul@example.com')
101 | self.assertEqual(accounts[2].email, 'peter@example.com')
102 |
103 | #
104 | # Delete
105 | #
106 |
107 | new_account.delete()
108 | self.assertIsNone(new_account.pk)
109 |
110 | accounts = Account.objects.all()
111 | self.assertEqual(accounts.count(), 2)
112 |
113 | #
114 | # Create with relationship
115 | #
116 |
117 | count_accounts = Account.objects.count()
118 | count_reporters = Reporter.objects.count()
119 | count_articles = Article.objects.count()
120 |
121 | new_account = Account()
122 | new_account.email = 'james@example.com'
123 | new_account.save()
124 |
125 | new_reporter = Reporter()
126 | new_reporter.account = new_account
127 | new_reporter.first_name = 'James'
128 | new_reporter.last_name = 'Doe'
129 | new_reporter.save()
130 |
131 | new_article = Article()
132 | new_article.headline = "James's story"
133 | new_article.reporter = new_reporter
134 | self.assertRaises(ROAException, new_article.save) # pub_date is required
135 | new_article.pub_date = now().date()
136 | new_article.save()
137 |
138 | # There are 3 lines where new_account is save:
139 | # new_account.save()
140 | # new_reporter.save()
141 | # new_article.save()
142 | # But only one instance must be save into db.
143 | # TODO: goal: only one record.
144 | self.assertEqual(Account.objects.count(), count_accounts + 3)
145 | # Same case:
146 | self.assertEqual(Reporter.objects.count(), count_reporters + 2)
147 | self.assertEqual(Article.objects.count(), count_articles + 1)
148 |
149 | article = Article.objects.get(headline="James's story")
150 | self.assertEqual(article.reporter.first_name, 'James')
151 | self.assertEqual(article.reporter.account.email, 'james@example.com')
152 |
153 |
154 | def test_empty_list_no_pagination(self):
155 | tags = Tag.objects.filter(label='idonetexist')
156 |
157 | # To check that iterating over the empty queryset works
158 | for i in tags:
159 | pass
160 |
161 | self.assertEqual(tags.count(), 0)
162 |
--------------------------------------------------------------------------------
/examples/django_roa_client/models.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from django.db import models
3 | from django.template.defaultfilters import slugify
4 |
5 | from django_roa import Model
6 |
7 | class RemotePage(Model):
8 | title = models.CharField(max_length=50, blank=True, null=True)
9 |
10 | def __unicode__(self):
11 | return u'%s (%s)' % (self.title, self.pk)
12 |
13 | @staticmethod
14 | def get_resource_url_list():
15 | return u'http://127.0.0.1:8081/django_roa_server/remotepage/'
16 |
17 |
18 | class RemotePageWithManyFields(Model):
19 | #auto_field = models.AutoField(primary_key=True)
20 | char_field = models.CharField(max_length=50, blank=True, null=True)
21 | date_field = models.DateField(blank=True, null=True)
22 | datetime_field = models.DateTimeField(blank=True, null=True)
23 | decimal_field = models.DecimalField(decimal_places=3, max_digits=5, blank=True, null=True)
24 | email_field = models.EmailField(blank=True, null=True)
25 | filepath_field = models.FilePathField(blank=True, null=True)
26 | float_field = models.FloatField(blank=True, null=True)
27 | integer_field = models.IntegerField(blank=True, null=True)
28 | ipaddress_field = models.IPAddressField(blank=True, null=True)
29 | positiveinteger_field = models.PositiveIntegerField(blank=True, null=True)
30 | positivesmallinteger_field = models.PositiveSmallIntegerField(blank=True, null=True)
31 | slug_field = models.SlugField(blank=True, null=True)
32 | smallinteger_field = models.SmallIntegerField(blank=True, null=True)
33 | text_field = models.TextField(blank=True, null=True)
34 | time_field = models.TimeField(blank=True, null=True)
35 | url_field = models.URLField(blank=True, null=True)
36 | xml_field = models.CharField(max_length=128, blank=True, null=True)
37 |
38 | file_field = models.FileField(upload_to="files", blank=True, null=True)
39 | image_field = models.ImageField(upload_to="images", blank=True, null=True)
40 |
41 | def __unicode__(self):
42 | return u'%s (%s)' % (self.__class__.__name__, self.pk)
43 |
44 | @staticmethod
45 | def get_resource_url_list():
46 | return u'http://127.0.0.1:8081/django_roa_server/remotepagewithmanyfields/'
47 |
48 |
49 | class RemotePageWithBooleanFields(Model):
50 | boolean_field = models.BooleanField()
51 | null_boolean_field = models.NullBooleanField()
52 |
53 | @staticmethod
54 | def get_resource_url_list():
55 | return u'http://127.0.0.1:8081/django_roa_server/remotepagewithbooleanfields/'
56 |
57 |
58 | class RemotePageWithCustomSlug(Model):
59 | title = models.CharField(max_length=50)
60 | slug = models.SlugField()
61 |
62 | def __unicode__(self):
63 | return u'%s (%s)' % (self.title, self.pk)
64 |
65 | def save(self, force_insert=False, force_update=False, using=None):
66 | if not self.slug:
67 | self.slug = slugify(self.title)
68 | super(RemotePageWithCustomSlug, self).save(force_insert, force_update)
69 |
70 | @staticmethod
71 | def get_resource_url_list():
72 | return u'http://127.0.0.1:8081/django_roa_server/remotepagewithcustomslug/'
73 |
74 | def get_resource_url_detail(self):
75 | return u"%s%s-%s/" % (self.get_resource_url_list(), self.pk, self.slug)
76 |
77 |
78 | class RemotePageWithOverriddenUrls(Model):
79 | title = models.CharField(max_length=50)
80 | slug = models.SlugField()
81 |
82 | def __unicode__(self):
83 | return u'%s (%s)' % (self.title, self.pk)
84 |
85 | def save(self, force_insert=False, force_update=False, using=None):
86 | if not self.slug:
87 | self.slug = slugify(self.title)
88 | super(RemotePageWithOverriddenUrls, self).save(force_insert, force_update)
89 |
90 | @staticmethod
91 | def get_resource_url_list():
92 | return u'' # overridden by settings
93 |
94 |
95 | class RemotePageWithRelationsThrough(Model):
96 | title = models.CharField(max_length=50)
97 | remote_page_with_relations = models.ForeignKey("RemotePageWithRelations", blank=True, null=True)
98 | remote_page_with_many_fields = models.ForeignKey("RemotePageWithManyFields", blank=True, null=True)
99 |
100 | def __unicode__(self):
101 | return u'%s (%s)' % (self.title, self.pk)
102 |
103 | @staticmethod
104 | def get_resource_url_list():
105 | return u'http://127.0.0.1:8081/django_roa_server/remotepagewithrelationsthrough/'
106 |
107 |
108 | class RemotePageWithRelations(Model):
109 | title = models.CharField(max_length=50)
110 | remote_page = models.ForeignKey(RemotePage, blank=True, null=True)
111 | remote_page_fields = models.ManyToManyField(RemotePageWithManyFields, through=RemotePageWithRelationsThrough, blank=True, null=True)
112 |
113 | def __unicode__(self):
114 | return u'%s (%s)' % (self.title, self.pk)
115 |
116 | @staticmethod
117 | def get_resource_url_list():
118 | return u'http://127.0.0.1:8081/django_roa_server/remotepagewithrelations/'
119 |
120 |
121 | class RemotePageWithNamedRelations(Model):
122 | title = models.CharField(max_length=50)
123 | first_page = models.ForeignKey(RemotePage, blank=True, null=True, related_name="from_first")
124 | last_page = models.ForeignKey(RemotePage, blank=True, null=True, related_name="from_last")
125 |
126 | def __unicode__(self):
127 | return u'%s (%s)' % (self.title, self.pk)
128 |
129 | @staticmethod
130 | def get_resource_url_list():
131 | return u'http://127.0.0.1:8081/django_roa_server/remotepagewithnamedrelations/'
132 |
133 |
134 | class RemotePageWithProxy(RemotePage):
135 |
136 | class Meta:
137 | proxy = True
138 |
139 | @staticmethod
140 | def get_resource_url_list():
141 | return u'http://127.0.0.1:8081/django_roa_server/remotepagewithproxy/'
142 |
143 | class RemotePageWithCustomPrimaryKey(Model):
144 | auto_field = models.AutoField(primary_key=True)
145 | title = models.CharField(max_length=50)
146 |
147 | def __unicode__(self):
148 | return u'%s (%s)' % (self.title, self.pk)
149 |
150 | @staticmethod
151 | def get_resource_url_list():
152 | return u'http://127.0.0.1:8081/django_roa_server/remotepagewithcustomprimarykey/'
153 |
154 | class RemotePageWithCustomPrimaryKeyCountOverridden(RemotePageWithCustomPrimaryKey):
155 |
156 | class Meta:
157 | proxy = True
158 |
159 | def get_resource_url_count(self):
160 | return u'http://127.0.0.1:8081/django_roa_server/remotepagewithcustomprimarykey/count2/'
161 |
--------------------------------------------------------------------------------
/CHANGELOG:
--------------------------------------------------------------------------------
1 | ======================
2 | Django-ROA's changelog
3 | ======================
4 |
5 | Version 1.8.1, 21 Nov 2014:
6 | --------------------------
7 |
8 | * Fix pagination in remote querysets (checking limit_start/limit_stop)
9 | Thanks to @jathanism
10 |
11 |
12 | Version 1.8, 12 Sep 2014:
13 | --------------------------
14 |
15 | * Django REST Framework support + bug fixes.
16 | Thanks to @JocelynDelalande, @sandersky, @jathanism and @charles-vdulac
17 |
18 |
19 | Version 1.7, 11 May 2012:
20 | --------------------------
21 |
22 | * @neokeeper aka Martin Skala updated the code in many ways: introduced an
23 | ROA_FILTERS setting to provide authentication on resources' access,
24 | improved single instance retrieval, dealt with pks instead of ids when
25 | possible, updated requirements, code clean up, documentation and
26 | example updates. Many thanks!
27 | * @NikhilChelliah fixed a bug related to object saving and non-int ids.
28 | Thanks!
29 |
30 |
31 | Version 1.6, 16 July 2010:
32 | --------------------------
33 |
34 | * Switch to pip's requirements.txt file for external dependencies (restkit and
35 | piston, note that the later is only useful for tests).
36 | * @cyril fixed a lot of bugs, see changeset 86a68259e83e for details. Thanks!
37 | * @cyrilrbt fixed missing ROA_CUSTOM_ARGS on delete. Thanks!
38 |
39 |
40 | Version 1.5, 7 January 2010:
41 | ----------------------------
42 |
43 | * Support for Django 1.2 alpha, no more patch required.
44 | * Handle ManyToMany relations only with the required "through" argument,
45 | it allows you to define a URL for this resource.
46 | * Update restkit to the latest tip (>0.9.2)
47 | * Update piston to the latest tip (>0.2.3rc1)
48 | * Deprecate "remoteauth" application for now, mapping of M2M relations is not
49 | fully compatible, you should still be able to use the User model though but
50 | Permissions and Groups' relation will not be correct anymore.
51 | This is still a work in progress, suggestions welcome!
52 |
53 |
54 | Version 1.4, 15 September 2009:
55 | -------------------------------
56 |
57 | * Support for named relations.
58 | * Support for proxy models.
59 | * Fix a bug to deal with ModelForms
60 | * Switch from restclient to the latest tip of restkit (>0.8.2)
61 | * Update piston to the latest tip (>0.2.3rc1)
62 | * Introduce a new settings, ROA_HEADERS, to specify headers sent to the server
63 | * Better errors' feedback from the server
64 |
65 |
66 | Version 1.3, 25 May 2009:
67 | -------------------------
68 |
69 | * First Open-Source release! (BSD license)
70 | * Handle ManyToMany relations in a RESTful way.
71 | * Support for unicode.
72 | * Support of custom de/serialization improved.
73 | * Addition of a Django command (inspectresources).
74 | * Add a couple of useful new settings (see documentation).
75 | * Switch tests to django-piston (0.2.1) and to unittests, use of logging.
76 | * Update py-restclient to the latest revision (1.3.2).
77 | * Backward incompatible changes: count is now handled by it's own URL via
78 | get_resource_url_count (default is get_resource_url_list/count/ but you can
79 | customize it).
80 |
81 |
82 | Version 1.2, 30 April 2009:
83 | ---------------------------
84 |
85 | * Update (Null)Boolean fields, Django doesn't allow anymore null=True.
86 | * Move documentation to BitBucket's wiki.
87 | * Update py-restclient to the latest revision (1.3).
88 |
89 |
90 | Version 1.1, 28 February 2009:
91 | ------------------------------
92 |
93 | * Add support for Groups and Permissions in remoteauth application (still
94 | depends on builtin Django's ContentTypes).
95 | * Add support for ModelForms with complex relations.
96 | * Add an example to document the use of a custom serializer.
97 | * Update py-restclient to the latest revision.
98 | * Backward incompatible changes: RemoteUser model has been renamed to User and
99 | ROAUserManager to UserManager to be consistent with the existing auth app.
100 |
101 |
102 | Version 1.0, 23 January 2009:
103 | -----------------------------
104 |
105 | * Add support for many-to-many relations.
106 | * Warning: Many-to-many relations depends on Django's issue #10109, apply the
107 | attached patch if you need it.
108 | * Update py-restclient to 1.1.4.
109 |
110 |
111 | Version 0.9, 9 January 2009:
112 | ----------------------------
113 |
114 | * Ease subclassing of MethodDispatcher with custom slugs.
115 |
116 |
117 | Version 0.8, 7 January 2009:
118 | ----------------------------
119 |
120 | * Add support for admin options except search_fields because of advanced
121 | querysets based on Q objects.
122 | * Support for ManyToMany relations in progress.
123 | * Warning: trunk version of Django is required for now, prior to revision 9695
124 | in order to handle FloatFields
125 | * Bugfixes: ROA_URL_OVERRIDES_* settings are optionnal and ForeignKey fields
126 | should work as expected.
127 | * Backward incompatible change: admin classes must inherit from
128 | django_roa.ModelAdmin to be consistent (see documentation and example).
129 |
130 |
131 | Version 0.7, 2 January 2009:
132 | ----------------------------
133 |
134 | * Add support for most useful fields (see specifications).
135 | * Warning: there are some Django bugs (hopefully with patches) which are
136 | required in order to use BooleanFields with None values and JSON
137 | serialization (#5563) or FloatFields which are considered as unicode
138 | (#9942). Patch your Django installation if you need those ones, that's why
139 | actual tests failed.
140 |
141 |
142 | Version 0.6, 28 December 2008:
143 | ------------------------------
144 |
145 | * Clean up remoteauth application.
146 | * Update py-restclient to 1.0.1.
147 |
148 |
149 | Version 0.5, 27 December 2008:
150 | ------------------------------
151 |
152 | * Backward incompatible change: Remote* classes had been renamed to ROA*, this
153 | is an internal change which should not affect your code.
154 | * Add support for ROA_URL_OVERRIDES_* settings and a complete example.
155 | * Declaration of default manager is no more required, if you inherit from
156 | django_roa.Model it will be added automatically given your ROA_MODELS
157 | setting.
158 | * Use the latest version of restclient (0.2.1), no more httplib2 dependency.
159 | This version uses pycurl, urllib2 or httplib2 given your configuration, in
160 | this order of preferences.
161 |
162 |
163 | Version 0.4, 27 December 2008:
164 | ------------------------------
165 |
166 | * Backward incompatible change: resource_url_list is no more defined in Meta
167 | class, now you must define your own get_resource_url_list static method in
168 | your Model class. resource_url_detail has been renamed as
169 | get_resource_url_detail for consistency and is no more a property.
170 | * Add support for ForeignKeys and XML serialization.
171 | * Bugfixes: DateTimeField and BooleanField should work as expected.
172 | * Add a complete example with a custom slug for multiple primary keys.
173 | * Ease debugging with a summary displayed if you use the test server instead
174 | of the whole HTML error.
175 |
176 |
177 | Version 0.3, 23 December 2008:
178 | ------------------------------
179 |
180 | * Backward incompatible change: resource_url_id is no more defined in Meta
181 | class, now you can define your own resource_url_detail property in your
182 | Model class. resource_url Meta attribute has been renamed as
183 | resource_url_list for consistency.
184 | * Bugfixes: RemoteQuerySet.count() and BooleanField should work as expected.
185 | * Better documentation, still need improvements.
186 |
187 |
188 | Version 0.2, 15 December 2008:
189 | ------------------------------
190 |
191 | Include restclient dependency.
192 |
193 |
194 | Version 0.1, 12 December 2008:
195 | ------------------------------
196 |
197 | Initial release.
198 |
--------------------------------------------------------------------------------
/ez_setup.py:
--------------------------------------------------------------------------------
1 | #!python
2 | """Bootstrap setuptools installation
3 |
4 | If you want to use setuptools in your package's setup.py, just include this
5 | file in the same directory with it, and add this to the top of your setup.py::
6 |
7 | from ez_setup import use_setuptools
8 | use_setuptools()
9 |
10 | If you want to require a specific version of setuptools, set a download
11 | mirror, or use an alternate download directory, you can do so by supplying
12 | the appropriate options to ``use_setuptools()``.
13 |
14 | This file can also be run as a script to install or upgrade setuptools.
15 | """
16 | import sys
17 | DEFAULT_VERSION = "0.6c9"
18 | DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
19 |
20 | md5_data = {
21 | 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
22 | 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
23 | 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
24 | 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
25 | 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
26 | 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
27 | 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
28 | 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
29 | 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
30 | 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
31 | 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
32 | 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
33 | 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
34 | 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
35 | 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
36 | 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
37 | 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
38 | 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
39 | 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
40 | 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
41 | 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
42 | 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
43 | 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
44 | 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
45 | 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
46 | 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
47 | 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
48 | 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902',
49 | 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de',
50 | 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b',
51 | 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03',
52 | 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a',
53 | 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6',
54 | 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a',
55 | }
56 |
57 | import sys, os
58 | try: from hashlib import md5
59 | except ImportError: from md5 import md5
60 |
61 | def _validate_md5(egg_name, data):
62 | if egg_name in md5_data:
63 | digest = md5(data).hexdigest()
64 | if digest != md5_data[egg_name]:
65 | print >>sys.stderr, (
66 | "md5 validation of %s failed! (Possible download problem?)"
67 | % egg_name
68 | )
69 | sys.exit(2)
70 | return data
71 |
72 | def use_setuptools(
73 | version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
74 | download_delay=15
75 | ):
76 | """Automatically find/download setuptools and make it available on sys.path
77 |
78 | `version` should be a valid setuptools version number that is available
79 | as an egg for download under the `download_base` URL (which should end with
80 | a '/'). `to_dir` is the directory where setuptools will be downloaded, if
81 | it is not already available. If `download_delay` is specified, it should
82 | be the number of seconds that will be paused before initiating a download,
83 | should one be required. If an older version of setuptools is installed,
84 | this routine will print a message to ``sys.stderr`` and raise SystemExit in
85 | an attempt to abort the calling script.
86 | """
87 | was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules
88 | def do_download():
89 | egg = download_setuptools(version, download_base, to_dir, download_delay)
90 | sys.path.insert(0, egg)
91 | import setuptools; setuptools.bootstrap_install_from = egg
92 | try:
93 | import pkg_resources
94 | except ImportError:
95 | return do_download()
96 | try:
97 | pkg_resources.require("setuptools>="+version); return
98 | except pkg_resources.VersionConflict, e:
99 | if was_imported:
100 | print >>sys.stderr, (
101 | "The required version of setuptools (>=%s) is not available, and\n"
102 | "can't be installed while this script is running. Please install\n"
103 | " a more recent version first, using 'easy_install -U setuptools'."
104 | "\n\n(Currently using %r)"
105 | ) % (version, e.args[0])
106 | sys.exit(2)
107 | else:
108 | del pkg_resources, sys.modules['pkg_resources'] # reload ok
109 | return do_download()
110 | except pkg_resources.DistributionNotFound:
111 | return do_download()
112 |
113 | def download_setuptools(
114 | version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
115 | delay = 15
116 | ):
117 | """Download setuptools from a specified location and return its filename
118 |
119 | `version` should be a valid setuptools version number that is available
120 | as an egg for download under the `download_base` URL (which should end
121 | with a '/'). `to_dir` is the directory where the egg will be downloaded.
122 | `delay` is the number of seconds to pause before an actual download attempt.
123 | """
124 | import urllib2, shutil
125 | egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
126 | url = download_base + egg_name
127 | saveto = os.path.join(to_dir, egg_name)
128 | src = dst = None
129 | if not os.path.exists(saveto): # Avoid repeated downloads
130 | try:
131 | from distutils import log
132 | if delay:
133 | log.warn("""
134 | ---------------------------------------------------------------------------
135 | This script requires setuptools version %s to run (even to display
136 | help). I will attempt to download it for you (from
137 | %s), but
138 | you may need to enable firewall access for this script first.
139 | I will start the download in %d seconds.
140 |
141 | (Note: if this machine does not have network access, please obtain the file
142 |
143 | %s
144 |
145 | and place it in this directory before rerunning this script.)
146 | ---------------------------------------------------------------------------""",
147 | version, download_base, delay, url
148 | ); from time import sleep; sleep(delay)
149 | log.warn("Downloading %s", url)
150 | src = urllib2.urlopen(url)
151 | # Read/write all in one block, so we don't create a corrupt file
152 | # if the download is interrupted.
153 | data = _validate_md5(egg_name, src.read())
154 | dst = open(saveto,"wb"); dst.write(data)
155 | finally:
156 | if src: src.close()
157 | if dst: dst.close()
158 | return os.path.realpath(saveto)
159 |
160 | def main(argv, version=DEFAULT_VERSION):
161 | """Install or upgrade setuptools and EasyInstall"""
162 | try:
163 | import setuptools
164 | except ImportError:
165 | egg = None
166 | try:
167 | egg = download_setuptools(version, delay=0)
168 | sys.path.insert(0,egg)
169 | from setuptools.command.easy_install import main
170 | return main(list(argv)+[egg]) # we're done here
171 | finally:
172 | if egg and os.path.exists(egg):
173 | os.unlink(egg)
174 | else:
175 | if setuptools.__version__ == '0.0.1':
176 | print >>sys.stderr, (
177 | "You have an obsolete version of setuptools installed. Please\n"
178 | "remove it from your system entirely before rerunning this script."
179 | )
180 | sys.exit(2)
181 |
182 | req = "setuptools>="+version
183 | import pkg_resources
184 | try:
185 | pkg_resources.require(req)
186 | except pkg_resources.VersionConflict:
187 | try:
188 | from setuptools.command.easy_install import main
189 | except ImportError:
190 | from easy_install import main
191 | main(list(argv)+[download_setuptools(delay=0)])
192 | sys.exit(0) # try to force an exit
193 | else:
194 | if argv:
195 | from setuptools.command.easy_install import main
196 | main(argv)
197 | else:
198 | print "Setuptools version",version,"or greater has been installed."
199 | print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
200 |
201 | def update_md5(filenames):
202 | """Update our built-in md5 registry"""
203 |
204 | import re
205 |
206 | for name in filenames:
207 | base = os.path.basename(name)
208 | f = open(name,'rb')
209 | md5_data[base] = md5(f.read()).hexdigest()
210 | f.close()
211 |
212 | data = [" %r: %r,\n" % it for it in md5_data.items()]
213 | data.sort()
214 | repl = "".join(data)
215 |
216 | import inspect
217 | srcfile = inspect.getsourcefile(sys.modules[__name__])
218 | f = open(srcfile, 'rb'); src = f.read(); f.close()
219 |
220 | match = re.search("\nmd5_data = {\n([^}]+)}", src)
221 | if not match:
222 | print >>sys.stderr, "Internal error!"
223 | sys.exit(2)
224 |
225 | src = src[:match.start(1)] + repl + src[match.end(1):]
226 | f = open(srcfile,'w')
227 | f.write(src)
228 | f.close()
229 |
230 |
231 | if __name__=='__main__':
232 | if len(sys.argv)>2 and sys.argv[1]=='--md5update':
233 | update_md5(sys.argv[2:])
234 | else:
235 | main(sys.argv[1:])
236 |
--------------------------------------------------------------------------------
/examples/django_roa_server/handlers.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from django.contrib.auth.models import User, Group, Permission
4 | try:
5 | from django.contrib.auth.models import Message
6 | except ImportError:
7 | Message = None
8 | from django.db import models
9 | from django.http import Http404
10 | from django.shortcuts import get_object_or_404, _get_queryset
11 |
12 | from piston.handler import BaseHandler, AnonymousBaseHandler
13 | from piston.utils import rc
14 |
15 | from django_roa_server.models import RemotePage, RemotePageWithManyFields, \
16 | RemotePageWithBooleanFields, RemotePageWithCustomSlug, \
17 | RemotePageWithOverriddenUrls, RemotePageWithRelations, \
18 | RemotePageWithNamedRelations, RemotePageWithRelationsThrough, \
19 | RemotePageWithCustomPrimaryKey
20 |
21 | logger = logging.getLogger("django_roa_server")
22 |
23 |
24 | class ROAHandler(BaseHandler):
25 |
26 | def flatten_dict(self, dct):
27 | return dict([ (str(k), dct.get(k)) for k in dct.keys() \
28 | if (k, dct.get(k)) != (u'id', u'None') and (k, dct.get(k)) != (u'pk', u'None')])
29 |
30 | @staticmethod
31 | def _get_object(model, *args, **kwargs):
32 | return get_object_or_404(model, pk=kwargs['pk'])
33 |
34 | def read(self, request, *args, **kwargs):
35 | """
36 | Retrieves an object or a list of objects.
37 | """
38 | if not self.has_model():
39 | return rc.NOT_IMPLEMENTED
40 |
41 | logger.debug('Args: %s' % str(args))
42 | logger.debug('Kwargs: %s' % str(kwargs))
43 |
44 | if kwargs.values() != [None]:
45 | # Returns a single object
46 | return [self._get_object(self.model, *args, **kwargs)]
47 |
48 | # Initialization
49 | queryset = _get_queryset(self.model)
50 | logger.debug('Before filters: %s' % str(queryset))
51 |
52 | # Filtering
53 | filters, excludes = {}, {}
54 | for k, v in request.GET.iteritems():
55 | if k.startswith('filter_'):
56 | filters[k[7:]] = v
57 | if k.startswith('exclude_'):
58 | excludes[k[8:]] = v
59 | queryset = queryset.filter(*filters.items()).exclude(*excludes.items())
60 |
61 | logger.debug('Filters: %s' % str(filters))
62 | logger.debug('Excludes: %s' % str(excludes))
63 | logger.debug('After filters: %s' % str(queryset))
64 |
65 | # Ordering (test custom parameters' name)
66 | if 'order' in request.GET:
67 | order_bys = request.GET['order'].split(',')
68 | queryset = queryset.order_by(*order_bys)
69 |
70 | # Slicing
71 | limit_start = int(request.GET.get('limit_start', 0))
72 | limit_stop = request.GET.get('limit_stop', False) and int(request.GET['limit_stop']) or None
73 | queryset = queryset[limit_start:limit_stop]
74 |
75 | obj_list = list(queryset)
76 | if not obj_list:
77 | raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
78 | logger.debug(u'Objects: %s retrieved' % [unicode(obj) for obj in obj_list])
79 | return queryset
80 |
81 | def create(self, request, *args, **kwargs):
82 | """
83 | Creates an object given request args, returned as a list.
84 | """
85 | if not self.has_model():
86 | return rc.NOT_IMPLEMENTED
87 |
88 | data = request.POST.copy()
89 |
90 | values = {}
91 | for field in self.model._meta.local_fields:
92 | field_value = data.get(field.name, None)
93 |
94 | if field_value not in (u'', u'None'):
95 |
96 | # Handle FK fields
97 | if field.rel and isinstance(field.rel, models.ManyToOneRel):
98 | field_value = data.get(field.attname, None)
99 | if field_value not in (u'', u'None'):
100 | values[field.attname] = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
101 | else:
102 | values[field.attname] = None
103 |
104 | # Handle all other fields
105 | else:
106 | if isinstance(field, models.fields.BooleanField):
107 | field_value = field.to_python(field_value)
108 | elif isinstance(field, models.fields.FloatField):
109 | if field_value is not None:
110 | field_value = float(field_value)
111 | values[field.name] = field_value
112 |
113 | obj = self.model.objects.create(**values)
114 |
115 | response = [self.model.objects.get(pk=obj.pk)]
116 | logger.debug(u'Object "%s" created' % unicode(obj))
117 | return response
118 |
119 | def update(self, request, *args, **kwargs):
120 | """
121 | Modifies an object given request args, returned as a list.
122 | """
123 | if not self.has_model():
124 | return rc.NOT_IMPLEMENTED
125 |
126 | data = request.PUT.copy()
127 | logger.debug(u'Received: %s as PUT data' % data)
128 | obj = self._get_object(self.model, *args, **kwargs)
129 |
130 | for field in self.model._meta.local_fields:
131 | field_name = field.name
132 |
133 | # Handle FK fields
134 | if field.rel and isinstance(field.rel, models.ManyToOneRel):
135 | field_value = data.get(field.attname, None)
136 | if field_value not in (u'', u'None', None):
137 | field_value = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
138 | else:
139 | field_value = None
140 | setattr(obj, field.attname, field_value)
141 |
142 | # Handle all other fields
143 | elif field_name in data:
144 | field_value = data[field_name]
145 | if field_value in (u'', u'None'):
146 | field_value = None
147 | if isinstance(field, models.fields.BooleanField) \
148 | or isinstance(field, models.fields.NullBooleanField) \
149 | or isinstance(field, models.fields.IntegerField):
150 | field_value = field.to_python(field_value)
151 | elif isinstance(field, models.fields.FloatField):
152 | if field_value is not None:
153 | field_value = float(field_value)
154 | elif isinstance(field, models.fields.CharField):
155 | if field_value is None:
156 | field_value = u''
157 | setattr(obj, field_name, field_value)
158 |
159 | obj.save()
160 |
161 | response = [self.model.objects.get(pk=obj.pk)]
162 | logger.debug(u'Object "%s" modified with %s' % (
163 | unicode(obj), unicode(data.items())))
164 | return response
165 |
166 | def delete(self, request, *args, **kwargs):
167 | """
168 | Deletes an object.
169 | """
170 | if not self.has_model():
171 | raise NotImplementedError
172 |
173 | try:
174 | obj = self._get_object(self.model, *args, **kwargs)
175 | obj.delete()
176 | logger.debug(u'Object "%s" deleted, remains %s' % (
177 | unicode(obj),
178 | [unicode(obj) for obj in self.model.objects.all()]))
179 |
180 | return rc.DELETED
181 | except self.model.MultipleObjectsReturned:
182 | return rc.DUPLICATE_ENTRY
183 | except self.model.DoesNotExist:
184 | return rc.NOT_HERE
185 |
186 |
187 | class ROACountHandler(BaseHandler):
188 | allowed_methods = ('GET', )
189 |
190 | def read(self, request, *args, **kwargs):
191 | """
192 | Retrieves the number of objects.
193 | """
194 | if not self.has_model():
195 | return rc.NOT_IMPLEMENTED
196 |
197 | # Initialization
198 | queryset = _get_queryset(self.model)
199 |
200 | # Filtering
201 | filters, excludes = {}, {}
202 | for k, v in request.GET.iteritems():
203 | if k.startswith('filter_'):
204 | filters[k[7:]] = v
205 | if k.startswith('exclude_'):
206 | excludes[k[8:]] = v
207 | queryset = queryset.filter(*filters.items()).exclude(*excludes.items())
208 |
209 | # Ordering
210 | if 'order_by' in request.GET:
211 | order_bys = request.GET['order_by'].split(',')
212 | queryset = queryset.order_by(*order_bys)
213 |
214 | # Counting
215 | counter = queryset.count()
216 | logger.debug(u'Count: %s objects' % counter)
217 | return counter
218 |
219 |
220 | class ROAWithSlugHandler(ROAHandler):
221 |
222 | @staticmethod
223 | def _get_object(model, *args, **kwargs):
224 | """Returns an object from a slug.
225 |
226 | Useful when the slug is a combination of many fields.
227 | """
228 | pk, slug = kwargs['object_slug'].split('-', 1)
229 | obj = get_object_or_404(model, pk=pk)
230 | return obj
231 |
232 |
233 | class RemotePageHandler(ROAHandler):
234 | model = RemotePage
235 |
236 | class RemotePageCountHandler(ROACountHandler):
237 | model = RemotePage
238 |
239 |
240 | class RemotePageWithManyFieldsHandler(ROAHandler):
241 | model = RemotePageWithManyFields
242 |
243 | class RemotePageWithManyFieldsCountHandler(ROACountHandler):
244 | model = RemotePageWithManyFields
245 |
246 |
247 | class RemotePageWithBooleanFieldsHandler(ROAHandler):
248 | model = RemotePageWithBooleanFields
249 |
250 | class RemotePageWithBooleanFieldsCountHandler(ROACountHandler):
251 | model = RemotePageWithBooleanFields
252 |
253 |
254 | class RemotePageWithCustomSlugHandler(ROAWithSlugHandler):
255 | model = RemotePageWithCustomSlug
256 |
257 | class RemotePageWithCustomSlugCountHandler(ROACountHandler):
258 | model = RemotePageWithCustomSlug
259 |
260 | class RemotePageWithCustomPrimaryKeyHandler(ROAHandler):
261 | model = RemotePageWithCustomPrimaryKey
262 |
263 | class RemotePageWithCustomPrimaryKeyCountHandler(ROACountHandler):
264 | model = RemotePageWithCustomPrimaryKey
265 |
266 | class RemotePageWithCustomPrimaryKeyCount2Handler(ROACountHandler):
267 | model = RemotePageWithCustomPrimaryKey
268 |
269 | def read(self, request, *args, **kwargs):
270 | return 'invalid counter'
271 |
272 |
273 | class RemotePageWithOverriddenUrlsHandler(ROAWithSlugHandler):
274 | model = RemotePageWithOverriddenUrls
275 |
276 | class RemotePageWithOverriddenUrlsCountHandler(ROACountHandler):
277 | model = RemotePageWithOverriddenUrls
278 |
279 |
280 | class RemotePageWithRelationsHandler(ROAHandler):
281 | model = RemotePageWithRelations
282 |
283 | class RemotePageWithRelationsCountHandler(ROACountHandler):
284 | model = RemotePageWithRelations
285 |
286 |
287 | class RemotePageWithRelationsThroughHandler(ROAHandler):
288 | model = RemotePageWithRelationsThrough
289 |
290 | class RemotePageWithRelationsThroughCountHandler(ROACountHandler):
291 | model = RemotePageWithRelationsThrough
292 |
293 |
294 | class RemotePageWithNamedRelationsHandler(ROAHandler):
295 | model = RemotePageWithNamedRelations
296 |
297 | class RemotePageWithNamedRelationsCountHandler(ROACountHandler):
298 | model = RemotePageWithNamedRelations
299 |
300 |
301 | class UserHandler(ROAHandler):
302 | model = User
303 |
304 | MessageHandler = None
305 | if Message:
306 | class MessageHandler(ROAHandler):
307 | model = Message
308 |
309 | class PermissionHandler(ROAHandler):
310 | model = Permission
311 |
312 | class GroupHandler(ROAHandler):
313 | model = Group
314 |
--------------------------------------------------------------------------------
/django_roa/db/query.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from StringIO import StringIO
3 |
4 | from django.conf import settings
5 | from django.db.models import query
6 | from django.core import serializers
7 | # Django >= 1.5
8 | from django_roa.db import get_roa_headers
9 |
10 | try:
11 | from django.db.models.constants import LOOKUP_SEP
12 | #Django < 1.4
13 | except:
14 | from django.db.models.sql.constants import LOOKUP_SEP
15 | from django.db.models.query_utils import Q
16 | from django.utils.encoding import force_unicode
17 |
18 | from restkit import Resource, ResourceNotFound
19 | from django_roa.db.exceptions import ROAException, ROANotImplementedYetException
20 |
21 | logger = logging.getLogger("django_roa")
22 |
23 | ROA_MODEL_NAME_MAPPING = getattr(settings, 'ROA_MODEL_NAME_MAPPING', [])
24 | ROA_ARGS_NAMES_MAPPING = getattr(settings, 'ROA_ARGS_NAMES_MAPPING', {})
25 | ROA_FORMAT = getattr(settings, 'ROA_FORMAT', 'json')
26 | ROA_FILTERS = getattr(settings, 'ROA_FILTERS', {})
27 | ROA_SSL_ARGS = getattr(settings, 'ROA_SSL_ARGS', {})
28 |
29 | DEFAULT_CHARSET = getattr(settings, 'DEFAULT_CHARSET', 'utf-8')
30 |
31 |
32 | class Query(object):
33 | def __init__(self):
34 | self.order_by = []
35 | self.extra_order_by = []
36 | self.default_ordering = []
37 | self.filters = {}
38 | self.excludes = {}
39 | self.search_term = None
40 | self.filterable = True
41 | self.limit_start = None
42 | self.limit_stop = None
43 | self.where = False
44 | self.select_related = False
45 | self.max_depth = None
46 | self.extra_select = {}
47 | self.select_for_update = False
48 |
49 | def can_filter(self):
50 | return self.filterable
51 |
52 | def clone(self):
53 | return self
54 |
55 | def clear_ordering(self):
56 | self.order_by = []
57 |
58 | def filter(self, *args, **kwargs):
59 | self.filters.update(kwargs)
60 |
61 | def search(self, search_term, limit_start=None, limit_stop=None):
62 | self.search_term = search_term
63 | self.limit_start = limit_start
64 | self.limit_stop = limit_stop
65 |
66 | def exclude(self, *args, **kwargs):
67 | self.excludes.update(kwargs)
68 |
69 | def set_limits(self, start=None, stop=None):
70 | self.limit_start = start
71 | self.limit_stop = stop
72 | self.filterable = False
73 |
74 | def add_select_related(self, fields):
75 | """
76 | Sets up the select_related data structure so that we only select
77 | certain related models (as opposed to all models, when
78 | self.select_related=True).
79 | """
80 | field_dict = {}
81 | for field in fields:
82 | d = field_dict
83 | for part in field.split(LOOKUP_SEP):
84 | d = d.setdefault(part, {})
85 | self.select_related = field_dict
86 | self.related_select_cols = []
87 | self.related_select_fields = []
88 |
89 | @property
90 | def parameters(self):
91 | """
92 | Returns useful parameters as a dictionary.
93 | """
94 | parameters = {}
95 |
96 | # Filtering
97 | for k, v in self.filters.iteritems():
98 | key = '%s%s' % (ROA_ARGS_NAMES_MAPPING.get('FILTER_', 'filter_'), k)
99 | # v could be an object
100 | try:
101 | v = v.id
102 | except:
103 | pass
104 |
105 | if key in ROA_ARGS_NAMES_MAPPING:
106 | parameters[ROA_ARGS_NAMES_MAPPING[key]] = v
107 | else:
108 | parameters[key] = v
109 | for k, v in self.excludes.iteritems():
110 | key = '%s%s' % (ROA_ARGS_NAMES_MAPPING.get('EXCLUDE_', 'exclude_'), k)
111 | if key in ROA_ARGS_NAMES_MAPPING:
112 | parameters[ROA_ARGS_NAMES_MAPPING[key]] = v
113 | else:
114 | parameters[key] = v
115 | if self.search_term:
116 | parameters[ROA_ARGS_NAMES_MAPPING.get('SEARCH_', 'search')] = self.search_term
117 |
118 | # Ordering
119 | if self.order_by:
120 | order_by = ','.join(self.order_by)
121 | parameters[ROA_ARGS_NAMES_MAPPING.get('ORDER_BY', 'order_by')] = order_by
122 |
123 | # Slicing
124 | if self.limit_start:
125 | parameters[ROA_ARGS_NAMES_MAPPING.get('LIMIT_START', 'limit_start')] = self.limit_start
126 | if self.limit_stop:
127 | parameters[ROA_ARGS_NAMES_MAPPING.get('LIMIT_STOP', 'limit_stop')] = self.limit_stop
128 |
129 | # Format
130 | parameters[ROA_ARGS_NAMES_MAPPING.get('FORMAT', 'format')] = ROA_FORMAT
131 |
132 | parameters.update(getattr(settings, 'ROA_CUSTOM_ARGS', {}))
133 | return parameters
134 |
135 | ##########################################
136 | # Fake methods required by admin options #
137 | ##########################################
138 |
139 | def add_fields(self, field_names, allow_m2m=True):
140 | """ Fake method. """
141 | pass
142 |
143 | def trim_extra_select(self, names):
144 | """ Fake method. """
145 | pass
146 |
147 | def results_iter(self):
148 | """ Fake method. """
149 | return []
150 |
151 | def combine(self, rhs, connector):
152 | """ Fake method. """
153 | pass
154 |
155 | def has_results(self, *args, **kwargs):
156 | """ Fake method. """
157 | return True
158 |
159 |
160 | class RemoteQuerySet(query.QuerySet):
161 | """
162 | QuerySet which access remote resources.
163 | """
164 | def __init__(self, model=None, query=None):
165 | self.model = model
166 | self.query = query or Query()
167 | self._result_cache = None
168 | self._iter = None
169 | self._sticky_filter = False
170 | self._db = False
171 | self._for_write = False
172 | self._hints = {}
173 |
174 | self.params = {}
175 |
176 | self._prefetch_related_lookups = False
177 |
178 | ########################
179 | # PYTHON MAGIC METHODS #
180 | ########################
181 |
182 | def __repr__(self):
183 | if not self.query.limit_start and not self.query.limit_stop:
184 | data = list(self[:query.REPR_OUTPUT_SIZE + 1])
185 | if len(data) > query.REPR_OUTPUT_SIZE:
186 | data[-1] = "...(remaining elements truncated)..."
187 | else:
188 | data = list(self)
189 | return repr(data)
190 |
191 | ####################################
192 | # METHODS THAT DO RESOURCE QUERIES #
193 | ####################################
194 |
195 | def iterator(self):
196 | """
197 | An iterator over the results from applying this QuerySet to the
198 | remote web service.
199 | """
200 | resource = Resource(self.model.get_resource_url_list(),
201 | filters=ROA_FILTERS, **ROA_SSL_ARGS)
202 | try:
203 | parameters = self.query.parameters
204 | logger.debug(u"""Requesting: "%s" through %s with parameters "%s" """ % (
205 | self.model.__name__,
206 | resource.uri,
207 | force_unicode(parameters)))
208 | response = resource.get(headers=self._get_http_headers(), **parameters)
209 | except ResourceNotFound:
210 | return
211 | except Exception as e:
212 | raise ROAException(e)
213 |
214 | response = force_unicode(response.body_string()).encode(DEFAULT_CHARSET)
215 |
216 | # Deserializing objects:
217 | data = self.model.get_parser().parse(StringIO(response))
218 |
219 | # Check limit_start and limit_stop arguments for pagination and only
220 | # slice data if they are both numeric and there are results left to go.
221 | # We only perform this check on lists.
222 | limit_start = getattr(self.query, 'limit_start', None)
223 | limit_stop = getattr(self.query, 'limit_stop', None)
224 | if (isinstance(limit_start, int) and isinstance(limit_stop, int) and
225 | limit_stop - limit_start < len(data) and limit_stop <= len(data) and
226 | isinstance(data, list)):
227 | data = data[limit_start:limit_stop]
228 |
229 | # [] is the case of empty no-paginated result
230 | if data != []:
231 | serializer = self.model.get_serializer(data=data)
232 | if not serializer.is_valid():
233 | raise ROAException(u'Invalid deserialization for %s model: %s' % (self.model, serializer.errors))
234 |
235 | for obj in serializer.object:
236 | yield obj
237 |
238 | def count(self):
239 | """
240 | Returns the number of records as an integer.
241 |
242 | The result is not cached nor comes from cache, cache must be handled
243 | by the server.
244 | """
245 | clone = self._clone()
246 |
247 | # Instantiation of clone.model is necessary because we can't set
248 | # a staticmethod for get_resource_url_count and avoid to set it
249 | # for all model without relying on get_resource_url_list
250 | instance = clone.model()
251 | resource = Resource(instance.get_resource_url_count(),
252 | filters=ROA_FILTERS, **ROA_SSL_ARGS)
253 | try:
254 | parameters = clone.query.parameters
255 | logger.debug(u"""Counting : "%s" through %s with parameters "%s" """ % (
256 | clone.model.__name__,
257 | resource.uri,
258 | force_unicode(parameters)))
259 | response = resource.get(headers=self._get_http_headers(), **parameters)
260 | except Exception as e:
261 | raise ROAException(e)
262 |
263 | response = force_unicode(response.body_string()).encode(DEFAULT_CHARSET)
264 | data = self.model.get_parser().parse(StringIO(response))
265 | return self.model.count_response(data)
266 |
267 | def _get_from_id_or_pk(self, id=None, pk=None, **kwargs):
268 | """
269 | Returns an object given an id or pk, request directly with the
270 | get_resource_url_detail method without filtering on ids
271 | (as Django's ORM do).
272 | """
273 | clone = self._clone()
274 |
275 | # Instantiation of clone.model is necessary because we can't set
276 | # a staticmethod for get_resource_url_detail and avoid to set it
277 | # for all model without relying on get_resource_url_list
278 | instance = clone.model()
279 | if pk is None:
280 | instance.id = id
281 | else:
282 | instance.pk = pk
283 | extra_args = {}
284 | extra_args.update(kwargs)
285 | extra_args.update(ROA_SSL_ARGS)
286 | resource = Resource(instance.get_resource_url_detail(),
287 | filters=ROA_FILTERS,
288 | **extra_args)
289 | try:
290 | parameters = clone.query.parameters
291 | logger.debug(u"""Retrieving : "%s" through %s with parameters "%s" """ % (
292 | clone.model.__name__,
293 | resource.uri,
294 | force_unicode(parameters)))
295 | response = resource.get(headers=self._get_http_headers(), **parameters)
296 | except Exception as e:
297 | raise ROAException(e)
298 |
299 | response = force_unicode(response.body_string()).encode(DEFAULT_CHARSET)
300 |
301 | for local_name, remote_name in ROA_MODEL_NAME_MAPPING:
302 | response = response.replace(remote_name, local_name)
303 |
304 | # Deserializing objects:
305 | data = self.model.get_parser().parse(StringIO(response))
306 | serializer = self.model.get_serializer(data=data)
307 | if not serializer.is_valid():
308 | raise ROAException(u'Invalid deserialization for %s model: %s' % (self.model, serializer.errors))
309 |
310 | return serializer.object
311 |
312 | def get(self, *args, **kwargs):
313 | """
314 | Performs the query and returns a single object matching the given
315 | keyword arguments.
316 | """
317 | # special case, get(id=X) directly request the resource URL and do not
318 | # filter on ids like Django's ORM do.
319 |
320 | # keep the custom attribute name of model for later use
321 | custom_pk = self.model._meta.pk.attname
322 | # search PK, ID or custom PK attribute name for exact match and get set
323 | # of unique matches
324 | attributes_set = set(attr for attr in ['id__exact', 'pk__exact', '%s__exact' % custom_pk] if attr in kwargs.keys())
325 | exact_match = list(attributes_set)
326 | # common way of getting particular object
327 | if kwargs.keys() == ['id']:
328 | return self._get_from_id_or_pk(id=kwargs['id'])
329 | # useful for admin which relies on PKs
330 | elif kwargs.keys() == ['pk']:
331 | return self._get_from_id_or_pk(pk=kwargs['pk'])
332 | # check the case of PK attribute with custom name
333 | elif kwargs.keys() == [custom_pk]:
334 | return self._get_from_id_or_pk(pk=kwargs[custom_pk])
335 | # check if there's an exact match filter
336 | elif len(exact_match) == 1:
337 | # use the value of exact match filter to retrieve object by PK
338 | return self._get_from_id_or_pk(pk=kwargs[exact_match[0]])
339 | else:
340 | # filter the request rather than retrieve it through get method
341 | return super(RemoteQuerySet, self).get(*args, **kwargs)
342 |
343 | def latest(self, field_name=None):
344 | """
345 | Returns the latest object, according to the model's 'get_latest_by'
346 | option or optional given field_name.
347 | """
348 | latest_by = field_name or self.model._meta.get_latest_by
349 | assert bool(latest_by), "latest() requires either a field_name parameter or 'get_latest_by' in the model"
350 |
351 | self.query.order_by.append('-%s' % latest_by)
352 | return self.iterator().next()
353 |
354 | def delete(self):
355 | """
356 | Deletes the records in the current QuerySet.
357 | """
358 | assert self.query.can_filter(), \
359 | "Cannot use 'limit' or 'offset' with delete."
360 |
361 | del_query = self._clone()
362 |
363 | # Disable non-supported fields.
364 | del_query.query.select_related = False
365 | del_query.query.clear_ordering()
366 |
367 | for obj in del_query:
368 | obj.delete()
369 |
370 | # Clear the result cache, in case this QuerySet gets reused.
371 | self._result_cache = None
372 | delete.alters_data = True
373 |
374 | ##################################################################
375 | # PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET #
376 | ##################################################################
377 |
378 | def filter(self, *args, **kwargs):
379 | """
380 | Returns a filtered QuerySet instance.
381 | """
382 | if args or kwargs:
383 | assert self.query.can_filter(), \
384 | "Cannot filter a query once a slice has been taken."
385 |
386 | clone = self._clone()
387 | clone.query.filter(*args, **kwargs)
388 | return clone
389 |
390 | def exclude(self, *args, **kwargs):
391 | """
392 | Returns a filtered QuerySet instance.
393 | """
394 | if args or kwargs:
395 | assert self.query.can_filter(), \
396 | "Cannot filter a query once a slice has been taken."
397 |
398 | clone = self._clone()
399 | clone.query.exclude(*args, **kwargs)
400 | return clone
401 |
402 | def search(self, search_term, limit_start=None, limit_stop=None, *args, **kwargs):
403 | """
404 | Returns a filtered QuerySet instance.
405 | """
406 | assert not(args or kwargs), "Search accept only one arg (search_term) and limit_start/limit_stop kwargs"
407 |
408 | clone = self._clone()
409 | clone.query.search(search_term, limit_start=limit_start, limit_stop=limit_stop)
410 | return clone
411 |
412 | def complex_filter(self, filter_obj):
413 | """
414 | Returns a new QuerySet instance with filter_obj added to the filters.
415 |
416 | filter_obj can be a Q object (or anything with an add_to_query()
417 | method) or a dictionary of keyword lookup arguments.
418 |
419 | This exists to support framework features such as 'limit_choices_to',
420 | and usually it will be more natural to use other methods.
421 | """
422 | if isinstance(filter_obj, Q) or hasattr(filter_obj, 'add_to_query'):
423 | raise ROAException('Not implemented yet')
424 | return self.filter(**filter_obj)
425 |
426 | def select_related(self, *fields, **kwargs):
427 | """
428 | Returns a new QuerySet instance that will select related objects.
429 |
430 | If fields are specified, they must be ForeignKey fields and only those
431 | related objects are included in the selection.
432 | """
433 | depth = kwargs.pop('depth', 0)
434 | if kwargs:
435 | raise TypeError('Unexpected keyword arguments to select_related: %s'
436 | % (kwargs.keys(),))
437 | obj = self._clone()
438 | if fields:
439 | if depth:
440 | raise TypeError('Cannot pass both "depth" and fields to select_related()')
441 | obj.query.add_select_related(fields)
442 | else:
443 | obj.query.select_related = True
444 | if depth:
445 | obj.query.max_depth = depth
446 | return obj
447 |
448 | def order_by(self, *field_names):
449 | """
450 | Returns a QuerySet instance with the ordering changed.
451 | """
452 | assert self.query.can_filter(), \
453 | "Cannot reorder a query once a slice has been taken."
454 |
455 | clone = self._clone()
456 | for field_name in field_names:
457 | clone.query.order_by.append(field_name)
458 | return clone
459 |
460 | def extra(self, select=None, where=None, params=None, tables=None,
461 | order_by=None, select_params=None):
462 | """
463 | Only to handle the case of the "cute trick" used in ModelForms (and
464 | per extension admin) for unique and date constraints.
465 |
466 | Example: ``.extra(select={'a': 1}).values('a').order_by()``.
467 |
468 | http://code.djangoproject.com/browser/django/trunk/django/forms/models.py#L322
469 | is an interesting documentation for details.
470 | """
471 | assert self.query.can_filter(), \
472 | "Cannot change a query once a slice has been taken"
473 | if select == {'a': 1}:
474 | # Totally hackish but we need a fake object to deal with
475 | # successive calls to values and order_by based on a count
476 | # which is the less expensive action for our implementation.
477 | class FakeInt(object):
478 | def __init__(self, count):
479 | self.count = count
480 |
481 | def values(self, *fields):
482 | if fields == ('a',): # double check that it's our case
483 | return self
484 |
485 | def order_by(self):
486 | return self.count
487 |
488 | return FakeInt(self.count())
489 | raise ROANotImplementedYetException('extra is not yet fully implemented.')
490 |
491 | ###################
492 | # PRIVATE METHODS #
493 | ###################
494 |
495 | def _clone(self, klass=None, setup=False, **kwargs):
496 | if klass is None:
497 | klass = self.__class__
498 | query = self.query.clone()
499 | if self._sticky_filter:
500 | query.filter_is_sticky = True
501 | c = klass(model=self.model, query=query)
502 | c.__dict__.update(kwargs)
503 | if setup and hasattr(c, '_setup_query'):
504 | c._setup_query()
505 | return c
506 |
507 | def _as_url(self):
508 | """
509 | Returns the internal query's URL and parameters
510 |
511 | as (u'url', {'arg_key': 'arg_value'}).
512 | """
513 | return self.model.get_resource_url_list(), self.query.parameters
514 |
515 |
516 | def _get_http_headers(self):
517 | return get_roa_headers()
518 |
--------------------------------------------------------------------------------
/django_roa/db/models.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import copy
3 | import logging
4 | from StringIO import StringIO
5 | from django.utils import six
6 |
7 | import django
8 |
9 | from django.conf import settings
10 | from django.core import serializers
11 | from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned,\
12 | FieldError
13 | from django.db import models
14 | from django.db.models import signals
15 | from django.db.models.options import Options
16 | from django.db.models.loading import register_models, get_model
17 | from django.db.models.base import ModelBase, subclass_exception, \
18 | get_absolute_url, method_get_order, method_set_order
19 | from django.db.models.fields.related import (OneToOneField, add_lazy_relation)
20 | from django.utils.functional import curry
21 | from django.core.serializers.base import DeserializationError
22 | from django.core.serializers.python import Deserializer as PythonDeserializer, _get_model
23 |
24 | from functools import update_wrapper
25 |
26 | from django.utils.encoding import force_unicode, smart_unicode
27 | from rest_framework.parsers import JSONParser, XMLParser, YAMLParser
28 | from rest_framework.renderers import JSONRenderer, XMLRenderer, YAMLRenderer
29 |
30 | from restkit import Resource, RequestFailed, ResourceNotFound
31 | from django_roa.db import get_roa_headers
32 | from django_roa.db.exceptions import ROAException
33 |
34 | logger = logging.getLogger("django_roa")
35 |
36 |
37 | DJANGO_LT_1_7 = django.VERSION[:2] < (1, 7)
38 | DJANGO_GT_1_4 = django.VERSION[:2] > (1, 4)
39 |
40 |
41 | ROA_ARGS_NAMES_MAPPING = getattr(settings, 'ROA_ARGS_NAMES_MAPPING', {})
42 | ROA_FORMAT = getattr(settings, 'ROA_FORMAT', 'json')
43 | ROA_FILTERS = getattr(settings, 'ROA_FILTERS', {})
44 | ROA_MODEL_NAME_MAPPING = getattr(settings, 'ROA_MODEL_NAME_MAPPING', [])
45 | ROA_MODEL_CREATE_MAPPING = getattr(settings, 'ROA_MODEL_CREATE_MAPPING', {})
46 | ROA_MODEL_UPDATE_MAPPING = getattr(settings, 'ROA_MODEL_UPDATE_MAPPING', {})
47 | ROA_CUSTOM_ARGS = getattr(settings, "ROA_CUSTOM_ARGS", {})
48 | ROA_SSL_ARGS = getattr(settings, 'ROA_SSL_ARGS', {})
49 |
50 | DEFAULT_CHARSET = getattr(settings, 'DEFAULT_CHARSET', 'utf-8')
51 |
52 |
53 | class ROAModelBase(ModelBase):
54 | def __new__(cls, name, bases, attrs):
55 | if DJANGO_LT_1_7:
56 | return cls._new_old_django(name, bases, attrs)
57 | else:
58 | return cls._new_recent_django(name, bases, attrs)
59 |
60 | @classmethod
61 | def _new_recent_django(cls, name, bases, attrs):
62 | """
63 | Exactly the same except the line with ``isinstance(b, ROAModelBase)``.
64 | """
65 | import warnings
66 | from django.apps.config import MODELS_MODULE_NAME
67 | from django.apps import apps
68 | from django.utils.deprecation import RemovedInDjango19Warning
69 |
70 | super_new = super(ModelBase, cls).__new__
71 |
72 | # Also ensure initialization is only performed for subclasses of Model
73 | # (excluding Model class itself).
74 | parents = [b for b in bases if isinstance(b, ROAModelBase)]
75 | if not parents:
76 | return super_new(cls, name, bases, attrs)
77 |
78 | # Create the class.
79 | module = attrs.pop('__module__')
80 | new_class = super_new(cls, name, bases, {'__module__': module})
81 | attr_meta = attrs.pop('Meta', None)
82 | abstract = getattr(attr_meta, 'abstract', False)
83 | if not attr_meta:
84 | meta = getattr(new_class, 'Meta', None)
85 | else:
86 | meta = attr_meta
87 | base_meta = getattr(new_class, '_meta', None)
88 |
89 | # Look for an application configuration to attach the model to.
90 | app_config = apps.get_containing_app_config(module)
91 |
92 | if getattr(meta, 'app_label', None) is None:
93 |
94 | if app_config is None:
95 | # If the model is imported before the configuration for its
96 | # application is created (#21719), or isn't in an installed
97 | # application (#21680), use the legacy logic to figure out the
98 | # app_label by looking one level up from the package or module
99 | # named 'models'. If no such package or module exists, fall
100 | # back to looking one level up from the module this model is
101 | # defined in.
102 |
103 | # For 'django.contrib.sites.models', this would be 'sites'.
104 | # For 'geo.models.places' this would be 'geo'.
105 |
106 | msg = (
107 | "Model class %s.%s doesn't declare an explicit app_label "
108 | "and either isn't in an application in INSTALLED_APPS or "
109 | "else was imported before its application was loaded. " %
110 | (module, name))
111 | if abstract:
112 | msg += "Its app_label will be set to None in Django 1.9."
113 | else:
114 | msg += "This will no longer be supported in Django 1.9."
115 | warnings.warn(msg, RemovedInDjango19Warning, stacklevel=2)
116 |
117 | model_module = sys.modules[new_class.__module__]
118 | package_components = model_module.__name__.split('.')
119 | package_components.reverse() # find the last occurrence of 'models'
120 | try:
121 | app_label_index = package_components.index(MODELS_MODULE_NAME) + 1
122 | except ValueError:
123 | app_label_index = 1
124 | kwargs = {"app_label": package_components[app_label_index]}
125 |
126 | else:
127 | kwargs = {"app_label": app_config.label}
128 |
129 | else:
130 | kwargs = {}
131 |
132 | new_class.add_to_class('_meta', Options(meta, **kwargs))
133 | if not abstract:
134 | new_class.add_to_class(
135 | 'DoesNotExist',
136 | subclass_exception(
137 | str('DoesNotExist'),
138 | tuple(x.DoesNotExist for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (ObjectDoesNotExist,),
139 | module,
140 | attached_to=new_class))
141 | new_class.add_to_class(
142 | 'MultipleObjectsReturned',
143 | subclass_exception(
144 | str('MultipleObjectsReturned'),
145 | tuple(x.MultipleObjectsReturned for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (MultipleObjectsReturned,),
146 | module,
147 | attached_to=new_class))
148 | if base_meta and not base_meta.abstract:
149 | # Non-abstract child classes inherit some attributes from their
150 | # non-abstract parent (unless an ABC comes before it in the
151 | # method resolution order).
152 | if not hasattr(meta, 'ordering'):
153 | new_class._meta.ordering = base_meta.ordering
154 | if not hasattr(meta, 'get_latest_by'):
155 | new_class._meta.get_latest_by = base_meta.get_latest_by
156 |
157 | is_proxy = new_class._meta.proxy
158 |
159 | # If the model is a proxy, ensure that the base class
160 | # hasn't been swapped out.
161 | if is_proxy and base_meta and base_meta.swapped:
162 | raise TypeError("%s cannot proxy the swapped model '%s'." % (name, base_meta.swapped))
163 |
164 | if getattr(new_class, '_default_manager', None):
165 | if not is_proxy:
166 | # Multi-table inheritance doesn't inherit default manager from
167 | # parents.
168 | new_class._default_manager = None
169 | new_class._base_manager = None
170 | else:
171 | # Proxy classes do inherit parent's default manager, if none is
172 | # set explicitly.
173 | new_class._default_manager = new_class._default_manager._copy_to_model(new_class)
174 | new_class._base_manager = new_class._base_manager._copy_to_model(new_class)
175 |
176 | # Add all attributes to the class.
177 | for obj_name, obj in attrs.items():
178 | new_class.add_to_class(obj_name, obj)
179 |
180 | # All the fields of any type declared on this model
181 | new_fields = (
182 | new_class._meta.local_fields +
183 | new_class._meta.local_many_to_many +
184 | new_class._meta.virtual_fields
185 | )
186 | field_names = set(f.name for f in new_fields)
187 |
188 | # Basic setup for proxy models.
189 | if is_proxy:
190 | base = None
191 | for parent in [kls for kls in parents if hasattr(kls, '_meta')]:
192 | if parent._meta.abstract:
193 | if parent._meta.fields:
194 | raise TypeError("Abstract base class containing model fields not permitted for proxy model '%s'." % name)
195 | else:
196 | continue
197 | if base is not None:
198 | raise TypeError("Proxy model '%s' has more than one non-abstract model base class." % name)
199 | else:
200 | base = parent
201 | if base is None:
202 | raise TypeError("Proxy model '%s' has no non-abstract model base class." % name)
203 | new_class._meta.setup_proxy(base)
204 | new_class._meta.concrete_model = base._meta.concrete_model
205 | else:
206 | new_class._meta.concrete_model = new_class
207 |
208 | # Collect the parent links for multi-table inheritance.
209 | parent_links = {}
210 | for base in reversed([new_class] + parents):
211 | # Conceptually equivalent to `if base is Model`.
212 | if not hasattr(base, '_meta'):
213 | continue
214 | # Skip concrete parent classes.
215 | if base != new_class and not base._meta.abstract:
216 | continue
217 | # Locate OneToOneField instances.
218 | for field in base._meta.local_fields:
219 | if isinstance(field, OneToOneField):
220 | parent_links[field.rel.to] = field
221 |
222 | # Do the appropriate setup for any model parents.
223 | for base in parents:
224 | original_base = base
225 | if not hasattr(base, '_meta'):
226 | # Things without _meta aren't functional models, so they're
227 | # uninteresting parents.
228 | continue
229 |
230 | parent_fields = base._meta.local_fields + base._meta.local_many_to_many
231 | # Check for clashes between locally declared fields and those
232 | # on the base classes (we cannot handle shadowed fields at the
233 | # moment).
234 | for field in parent_fields:
235 | if field.name in field_names:
236 | raise FieldError(
237 | 'Local field %r in class %r clashes '
238 | 'with field of similar name from '
239 | 'base class %r' % (field.name, name, base.__name__)
240 | )
241 | if not base._meta.abstract:
242 | # Concrete classes...
243 | base = base._meta.concrete_model
244 | if base in parent_links:
245 | field = parent_links[base]
246 | elif not is_proxy:
247 | attr_name = '%s_ptr' % base._meta.model_name
248 | field = OneToOneField(base, name=attr_name,
249 | auto_created=True, parent_link=True)
250 | # Only add the ptr field if it's not already present;
251 | # e.g. migrations will already have it specified
252 | if not hasattr(new_class, attr_name):
253 | new_class.add_to_class(attr_name, field)
254 | else:
255 | field = None
256 | new_class._meta.parents[base] = field
257 | else:
258 | # .. and abstract ones.
259 | for field in parent_fields:
260 | new_class.add_to_class(field.name, copy.deepcopy(field))
261 |
262 | # Pass any non-abstract parent classes onto child.
263 | new_class._meta.parents.update(base._meta.parents)
264 |
265 | # Inherit managers from the abstract base classes.
266 | new_class.copy_managers(base._meta.abstract_managers)
267 |
268 | # Proxy models inherit the non-abstract managers from their base,
269 | # unless they have redefined any of them.
270 | if is_proxy:
271 | new_class.copy_managers(original_base._meta.concrete_managers)
272 |
273 | # Inherit virtual fields (like GenericForeignKey) from the parent
274 | # class
275 | for field in base._meta.virtual_fields:
276 | if base._meta.abstract and field.name in field_names:
277 | raise FieldError(
278 | 'Local field %r in class %r clashes '
279 | 'with field of similar name from '
280 | 'abstract base class %r' % (field.name, name, base.__name__)
281 | )
282 | new_class.add_to_class(field.name, copy.deepcopy(field))
283 |
284 | if abstract:
285 | # Abstract base models can't be instantiated and don't appear in
286 | # the list of models for an app. We do the final setup for them a
287 | # little differently from normal models.
288 | attr_meta.abstract = False
289 | new_class.Meta = attr_meta
290 | return new_class
291 |
292 | new_class._prepare()
293 | new_class._meta.apps.register_model(new_class._meta.app_label, new_class)
294 | return new_class
295 |
296 | @classmethod
297 | def _new_old_django(cls, name, bases, attrs):
298 | """
299 | Exactly the same except the line with ``isinstance(b, ROAModelBase)`` and part delimited by 'ROA HACK'
300 | """
301 | super_new = super(ModelBase, cls).__new__
302 |
303 | # six.with_metaclass() inserts an extra class called 'NewBase' in the
304 | # inheritance tree: Model -> NewBase -> object. But the initialization
305 | # should be executed only once for a given model class.
306 |
307 | # attrs will never be empty for classes declared in the standard way
308 | # (ie. with the `class` keyword). This is quite robust.
309 | if name == 'NewBase' and attrs == {}:
310 | return super_new(cls, name, bases, attrs)
311 |
312 | # Also ensure initialization is only performed for subclasses of Model
313 | # (excluding Model class itself).
314 | parents = [b for b in bases if isinstance(b, ROAModelBase) and
315 | not (b.__name__ == 'NewBase' and b.__mro__ == (b, object))]
316 | if not parents:
317 | return super_new(cls, name, bases, attrs)
318 |
319 | # Create the class.
320 | module = attrs.pop('__module__')
321 | new_class = super_new(cls, name, bases, {'__module__': module})
322 | attr_meta = attrs.pop('Meta', None)
323 | abstract = getattr(attr_meta, 'abstract', False)
324 | if not attr_meta:
325 | meta = getattr(new_class, 'Meta', None)
326 | else:
327 | meta = attr_meta
328 | base_meta = getattr(new_class, '_meta', None)
329 |
330 | if getattr(meta, 'app_label', None) is None:
331 | # Figure out the app_label by looking one level up.
332 | # For 'django.contrib.sites.models', this would be 'sites'.
333 | model_module = sys.modules[new_class.__module__]
334 | kwargs = {"app_label": model_module.__name__.split('.')[-2]}
335 | else:
336 | kwargs = {}
337 |
338 | new_class.add_to_class('_meta', Options(meta, **kwargs))
339 | if not abstract:
340 | # ROA HACK:
341 |
342 | subclass_kwargs = {
343 | 'name': str('DoesNotExist'),
344 | 'parents': tuple(x.DoesNotExist for x in parents if hasattr(x, '_meta') and not x._meta.abstract)
345 | or (ObjectDoesNotExist,),
346 | 'module': module
347 | }
348 | if DJANGO_GT_1_4:
349 | subclass_kwargs['attached_to'] = new_class
350 |
351 | new_class.add_to_class('DoesNotExist', subclass_exception(**subclass_kwargs))
352 |
353 | subclass_kwargs = {
354 | 'name': str('MultipleObjectsReturned'),
355 | 'parents': tuple(x.MultipleObjectsReturned for x in parents if hasattr(x, '_meta') and not x._meta.abstract)
356 | or (MultipleObjectsReturned,),
357 | 'module': module
358 | }
359 | if DJANGO_GT_1_4:
360 | subclass_kwargs['attached_to'] = new_class
361 |
362 | new_class.add_to_class('MultipleObjectsReturned', subclass_exception(**subclass_kwargs))
363 |
364 | # END HACK
365 |
366 | if base_meta and not base_meta.abstract:
367 | # Non-abstract child classes inherit some attributes from their
368 | # non-abstract parent (unless an ABC comes before it in the
369 | # method resolution order).
370 | if not hasattr(meta, 'ordering'):
371 | new_class._meta.ordering = base_meta.ordering
372 | if not hasattr(meta, 'get_latest_by'):
373 | new_class._meta.get_latest_by = base_meta.get_latest_by
374 |
375 | is_proxy = new_class._meta.proxy
376 |
377 | # If the model is a proxy, ensure that the base class
378 | # hasn't been swapped out.
379 | if is_proxy and base_meta and base_meta.swapped:
380 | raise TypeError("%s cannot proxy the swapped model '%s'." % (name, base_meta.swapped))
381 |
382 | if getattr(new_class, '_default_manager', None):
383 | if not is_proxy:
384 | # Multi-table inheritance doesn't inherit default manager from
385 | # parents.
386 | new_class._default_manager = None
387 | new_class._base_manager = None
388 | else:
389 | # Proxy classes do inherit parent's default manager, if none is
390 | # set explicitly.
391 | new_class._default_manager = new_class._default_manager._copy_to_model(new_class)
392 | new_class._base_manager = new_class._base_manager._copy_to_model(new_class)
393 |
394 | # Bail out early if we have already created this class.
395 | m = get_model(new_class._meta.app_label, name,
396 | seed_cache=False, only_installed=False)
397 | if m is not None:
398 | return m
399 |
400 | # Add all attributes to the class.
401 | for obj_name, obj in attrs.items():
402 | new_class.add_to_class(obj_name, obj)
403 |
404 | # All the fields of any type declared on this model
405 | new_fields = new_class._meta.local_fields + \
406 | new_class._meta.local_many_to_many + \
407 | new_class._meta.virtual_fields
408 | field_names = set([f.name for f in new_fields])
409 |
410 | # Basic setup for proxy models.
411 | if is_proxy:
412 | base = None
413 | for parent in [cls for cls in parents if hasattr(cls, '_meta')]:
414 | if parent._meta.abstract:
415 | if parent._meta.fields:
416 | raise TypeError("Abstract base class containing model fields not permitted for proxy model '%s'." % name)
417 | else:
418 | continue
419 | if base is not None:
420 | raise TypeError("Proxy model '%s' has more than one non-abstract model base class." % name)
421 | else:
422 | base = parent
423 | if base is None:
424 | raise TypeError("Proxy model '%s' has no non-abstract model base class." % name)
425 | if (new_class._meta.local_fields or
426 | new_class._meta.local_many_to_many):
427 | raise FieldError("Proxy model '%s' contains model fields." % name)
428 | new_class._meta.setup_proxy(base)
429 | new_class._meta.concrete_model = base._meta.concrete_model
430 | else:
431 | new_class._meta.concrete_model = new_class
432 |
433 | # Do the appropriate setup for any model parents.
434 | o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields
435 | if isinstance(f, OneToOneField)])
436 |
437 | for base in parents:
438 | original_base = base
439 | if not hasattr(base, '_meta'):
440 | # Things without _meta aren't functional models, so they're
441 | # uninteresting parents.
442 | continue
443 |
444 | parent_fields = base._meta.local_fields + base._meta.local_many_to_many
445 | # Check for clashes between locally declared fields and those
446 | # on the base classes (we cannot handle shadowed fields at the
447 | # moment).
448 | for field in parent_fields:
449 | if field.name in field_names:
450 | raise FieldError('Local field %r in class %r clashes '
451 | 'with field of similar name from '
452 | 'base class %r' %
453 | (field.name, name, base.__name__))
454 | if not base._meta.abstract:
455 | # Concrete classes...
456 | base = base._meta.concrete_model
457 | if base in o2o_map:
458 | field = o2o_map[base]
459 | elif not is_proxy:
460 | attr_name = '%s_ptr' % base._meta.model_name
461 | field = OneToOneField(base, name=attr_name,
462 | auto_created=True, parent_link=True)
463 | new_class.add_to_class(attr_name, field)
464 | else:
465 | field = None
466 | new_class._meta.parents[base] = field
467 | else:
468 | # .. and abstract ones.
469 | for field in parent_fields:
470 | new_class.add_to_class(field.name, copy.deepcopy(field))
471 |
472 | # Pass any non-abstract parent classes onto child.
473 | new_class._meta.parents.update(base._meta.parents)
474 |
475 | # Inherit managers from the abstract base classes.
476 | new_class.copy_managers(base._meta.abstract_managers)
477 |
478 | # Proxy models inherit the non-abstract managers from their base,
479 | # unless they have redefined any of them.
480 | if is_proxy:
481 | new_class.copy_managers(original_base._meta.concrete_managers)
482 |
483 | # Inherit virtual fields (like GenericForeignKey) from the parent
484 | # class
485 | for field in base._meta.virtual_fields:
486 | if base._meta.abstract and field.name in field_names:
487 | raise FieldError('Local field %r in class %r clashes '\
488 | 'with field of similar name from '\
489 | 'abstract base class %r' % \
490 | (field.name, name, base.__name__))
491 | new_class.add_to_class(field.name, copy.deepcopy(field))
492 |
493 | if abstract:
494 | # Abstract base models can't be instantiated and don't appear in
495 | # the list of models for an app. We do the final setup for them a
496 | # little differently from normal models.
497 | attr_meta.abstract = False
498 | new_class.Meta = attr_meta
499 | return new_class
500 |
501 | new_class._prepare()
502 | register_models(new_class._meta.app_label, new_class)
503 |
504 | # Because of the way imports happen (recursively), we may or may not be
505 | # the first time this model tries to register with the framework. There
506 | # should only be one class for each model, so we always return the
507 | # registered version.
508 | return get_model(new_class._meta.app_label, name,
509 | seed_cache=False, only_installed=False)
510 |
511 | def _prepare(cls):
512 | """
513 | Creates some methods once self._meta has been populated.
514 | """
515 | opts = cls._meta
516 | opts._prepare(cls)
517 |
518 | if opts.order_with_respect_to:
519 | cls.get_next_in_order = curry(cls._get_next_or_previous_in_order, is_next=True)
520 | cls.get_previous_in_order = curry(cls._get_next_or_previous_in_order, is_next=False)
521 |
522 | # defer creating accessors on the foreign class until we are
523 | # certain it has been created
524 | def make_foreign_order_accessors(field, model, cls):
525 | setattr(
526 | field.rel.to,
527 | 'get_%s_order' % cls.__name__.lower(),
528 | curry(method_get_order, cls)
529 | )
530 | setattr(
531 | field.rel.to,
532 | 'set_%s_order' % cls.__name__.lower(),
533 | curry(method_set_order, cls)
534 | )
535 | add_lazy_relation(
536 | cls,
537 | opts.order_with_respect_to,
538 | opts.order_with_respect_to.rel.to,
539 | make_foreign_order_accessors
540 | )
541 |
542 | # Give the class a docstring -- its definition.
543 | if cls.__doc__ is None:
544 | cls.__doc__ = "%s(%s)" % (cls.__name__, ", ".join([f.attname for f in opts.fields]))
545 |
546 | if hasattr(cls, 'get_absolute_url'):
547 | cls.get_absolute_url = update_wrapper(curry(get_absolute_url, opts, cls.get_absolute_url),
548 | cls.get_absolute_url)
549 |
550 | if hasattr(cls, 'get_resource_url_list'):
551 | cls.get_resource_url_list = staticmethod(curry(get_resource_url_list,
552 | opts, cls.get_resource_url_list))
553 |
554 | if hasattr(cls, 'get_resource_url_count'):
555 | cls.get_resource_url_count = update_wrapper(curry(get_resource_url_count, opts, cls.get_resource_url_count),
556 | cls.get_resource_url_count)
557 |
558 | if hasattr(cls, 'get_resource_url_detail'):
559 | cls.get_resource_url_detail = update_wrapper(curry(get_resource_url_detail, opts, cls.get_resource_url_detail),
560 | cls.get_resource_url_detail)
561 |
562 | signals.class_prepared.send(sender=cls)
563 |
564 |
565 | class ROAModel(models.Model):
566 | """
567 | Model which access remote resources.
568 | """
569 | __metaclass__ = ROAModelBase
570 |
571 | @classmethod
572 | def serializer(cls):
573 | """
574 | Return a like Django Rest Framework serializer class
575 | """
576 | raise NotImplementedError
577 |
578 | def get_renderer(self):
579 | """
580 | Cf from rest_framework.renderers import JSONRenderer
581 | """
582 | if ROA_FORMAT == 'json':
583 | return JSONRenderer()
584 | elif ROA_FORMAT == 'xml':
585 | return XMLRenderer()
586 | elif ROAException == 'yaml':
587 | return YAMLRenderer()
588 | else:
589 | raise NotImplementedError
590 |
591 | @classmethod
592 | def get_parser(cls):
593 | """
594 | Cf from rest_framework.parsers import JSONParser
595 | """
596 | if ROA_FORMAT == 'json':
597 | return JSONParser()
598 | elif ROA_FORMAT == 'xml':
599 | return XMLParser()
600 | elif ROAException == 'yaml':
601 | return YAMLParser()
602 | else:
603 | raise NotImplementedError
604 |
605 | def get_serializer_content_type(self):
606 | if ROA_FORMAT == 'json':
607 | return {'Content-Type' : 'application/json'}
608 | elif ROA_FORMAT == 'xml':
609 | return {'Content-Type' : 'application/xml'}
610 | elif ROAException == 'yaml':
611 | return {'Content-Type' : 'text/x-yaml'}
612 | else:
613 | raise NotImplementedError
614 |
615 | @classmethod
616 | def get_serializer(cls, instance=None, data=None, partial=False, **kwargs):
617 | """
618 | Transform API response to Django model objects.
619 | """
620 | serializer_class = cls.serializer()
621 | serializer = None
622 |
623 | if instance:
624 | serializer = serializer_class(instance, partial=partial, **kwargs)
625 | elif data:
626 | data = data['results'] if 'results' in data else data
627 | serializer = serializer_class(data=data, many=isinstance(data, list), **kwargs)
628 |
629 | return serializer
630 |
631 | @staticmethod
632 | def get_resource_url_list():
633 | raise Exception("Static method get_resource_url_list is not defined.")
634 |
635 | @classmethod
636 | def count_response(cls, data, **kwargs):
637 | """
638 | Read count query response and return result
639 | """
640 | if 'count' in data: # with default DRF : with pagination
641 | count = int(data['count'])
642 | elif isinstance(data, (list, tuple)):
643 | count = len(data) # with default DRF : without pagination
644 | else:
645 | count = int(data)
646 | return count
647 |
648 | def get_resource_url_count(self):
649 | # By default this method is not with compatible with json Django Rest Framework standard viewset urls
650 | # In this case, you just have to override it and return self.get_resource_url_list()
651 | return u"%scount/" % (self.get_resource_url_list(),)
652 |
653 | def get_resource_url_detail(self):
654 | return u"%s%s/" % (self.get_resource_url_list(), self.pk)
655 |
656 | def save_base(self, raw=False, cls=None, origin=None, force_insert=False,
657 | force_update=False, using=None, update_fields=None):
658 | """
659 | Does the heavy-lifting involved in saving. Subclasses shouldn't need to
660 | override this method. It's separate from save() in order to hide the
661 | need for overrides of save() to pass around internal-only parameters
662 | ('raw', 'cls', and 'origin').
663 | """
664 |
665 | assert not (force_insert and force_update)
666 |
667 | record_exists = False
668 |
669 | if cls is None:
670 | cls = self.__class__
671 | meta = cls._meta
672 | if not meta.proxy:
673 | origin = cls
674 | else:
675 | meta = cls._meta
676 |
677 | if origin and not getattr(meta, "auto_created", False):
678 | signals.pre_save.send(sender=origin, instance=self, raw=raw)
679 |
680 | model_name = str(meta)
681 |
682 | # If we are in a raw save, save the object exactly as presented.
683 | # That means that we don't try to be smart about saving attributes
684 | # that might have come from the parent class - we just save the
685 | # attributes we have been given to the class we have been given.
686 | # We also go through this process to defer the save of proxy objects
687 | # to their actual underlying model.
688 | if not raw or meta.proxy:
689 | if meta.proxy:
690 | org = cls
691 | else:
692 | org = None
693 | for parent, field in meta.parents.items():
694 | # At this point, parent's primary key field may be unknown
695 | # (for example, from administration form which doesn't fill
696 | # this field). If so, fill it.
697 | if field and getattr(self, parent._meta.pk.attname) is None and getattr(self, field.attname) is not None:
698 | setattr(self, parent._meta.pk.attname, getattr(self, field.attname))
699 |
700 | self.save_base(cls=parent, origin=org, using=using)
701 |
702 | if field:
703 | setattr(self, field.attname, self._get_pk_val(parent._meta))
704 | if meta.proxy:
705 | return
706 |
707 | if not meta.proxy:
708 | pk_val = self._get_pk_val(meta)
709 | pk_is_set = pk_val is not None
710 |
711 | get_args = {}
712 | get_args[ROA_ARGS_NAMES_MAPPING.get('FORMAT', 'format')] = ROA_FORMAT
713 | get_args.update(ROA_CUSTOM_ARGS)
714 |
715 | # Construct Json payload
716 | serializer = self.get_serializer(self)
717 | payload = self.get_renderer().render(serializer.data)
718 |
719 | # Add serializer content_type
720 | headers = get_roa_headers()
721 | headers.update(self.get_serializer_content_type())
722 |
723 | # check if resource use custom primary key
724 | if not meta.pk.attname in ['pk', 'id']:
725 | # consider it might be inserting so check it first
726 | # @todo: try to improve this block to check if custom pripary key is not None first
727 | resource = Resource(self.get_resource_url_detail(),
728 | filters=ROA_FILTERS, **ROA_SSL_ARGS)
729 | try:
730 | response = resource.get(payload=None, headers=headers, **get_args)
731 | except ResourceNotFound:
732 | # since such resource does not exist, it's actually creating
733 | pk_is_set = False
734 | except RequestFailed:
735 | pk_is_set = False
736 |
737 | if force_update or pk_is_set and not self.pk is None:
738 | record_exists = True
739 | resource = Resource(self.get_resource_url_detail(),
740 | filters=ROA_FILTERS, **ROA_SSL_ARGS)
741 | try:
742 | logger.debug(u"""Modifying : "%s" through %s with payload "%s" and GET args "%s" """ % (
743 | force_unicode(self),
744 | force_unicode(resource.uri),
745 | force_unicode(payload),
746 | force_unicode(get_args)))
747 | response = resource.put(payload=payload, headers=headers, **get_args)
748 | except RequestFailed as e:
749 | raise ROAException(e)
750 | else:
751 | record_exists = False
752 | resource = Resource(self.get_resource_url_list(),
753 | filters=ROA_FILTERS, **ROA_SSL_ARGS)
754 | try:
755 | logger.debug(u"""Creating : "%s" through %s with payload "%s" and GET args "%s" """ % (
756 | force_unicode(self),
757 | force_unicode(resource.uri),
758 | force_unicode(payload),
759 | force_unicode(get_args)))
760 | response = resource.post(payload=payload, headers=headers, **get_args)
761 | except RequestFailed as e:
762 | raise ROAException(e)
763 |
764 | response = force_unicode(response.body_string()).encode(DEFAULT_CHARSET)
765 |
766 | data = self.get_parser().parse(StringIO(response))
767 | serializer = self.get_serializer(data=data)
768 | if not serializer.is_valid():
769 | raise ROAException(u'Invalid deserialization for %s model: %s' % (self, serializer.errors))
770 | try:
771 | self.pk = int(serializer.object.pk)
772 | except ValueError:
773 | self.pk = serializer.object.pk
774 | self = serializer.object
775 |
776 | if origin:
777 | signals.post_save.send(sender=origin, instance=self,
778 | created=(not record_exists), raw=raw)
779 |
780 | save_base.alters_data = True
781 |
782 | def delete(self):
783 | assert self._get_pk_val() is not None, "%s object can't be deleted " \
784 | "because its %s attribute is set to None." \
785 | % (self._meta.object_name, self._meta.pk.attname)
786 |
787 | # Deletion in cascade should be done server side.
788 | resource = Resource(self.get_resource_url_detail(),
789 | filters=ROA_FILTERS, **ROA_SSL_ARGS)
790 |
791 | logger.debug(u"""Deleting : "%s" through %s""" % \
792 | (unicode(self), unicode(resource.uri)))
793 |
794 | # Add serializer content_type
795 | headers = get_roa_headers()
796 | headers.update(self.get_serializer_content_type())
797 |
798 | result = resource.delete(headers=headers, **ROA_CUSTOM_ARGS)
799 | if result.status_int in [200, 202, 204]:
800 | self.pk = None
801 |
802 | delete.alters_data = True
803 |
804 | def _get_unique_checks(self, exclude=None):
805 | """
806 | We don't want to check unicity that way for now.
807 | """
808 | unique_checks, date_checks = [], []
809 | return unique_checks, date_checks
810 |
811 |
812 | ##############################################
813 | # HELPER FUNCTIONS (CURRIED MODEL FUNCTIONS) #
814 | ##############################################
815 |
816 | ROA_URL_OVERRIDES_LIST = getattr(settings, 'ROA_URL_OVERRIDES_LIST', {})
817 | ROA_URL_OVERRIDES_COUNT = getattr(settings, 'ROA_URL_OVERRIDES_COUNT', {})
818 | ROA_URL_OVERRIDES_DETAIL = getattr(settings, 'ROA_URL_OVERRIDES_DETAIL', {})
819 |
820 |
821 | def get_resource_url_list(opts, func, *args, **kwargs):
822 | if DJANGO_LT_1_7:
823 | key = '%s.%s' % (opts.app_label, opts.module_name)
824 | else:
825 | key = '%s.%s' % (opts.app_label, opts.model_name)
826 |
827 | overridden = ROA_URL_OVERRIDES_LIST.get(key, False)
828 | return overridden and overridden or func(*args, **kwargs)
829 |
830 |
831 | def get_resource_url_count(opts, func, self, *args, **kwargs):
832 | if DJANGO_LT_1_7:
833 | key = '%s.%s' % (opts.app_label, opts.module_name)
834 | else:
835 | key = '%s.%s' % (opts.app_label, opts.model_name)
836 |
837 | return ROA_URL_OVERRIDES_COUNT.get(key, func)(self, *args, **kwargs)
838 |
839 |
840 | def get_resource_url_detail(opts, func, self, *args, **kwargs):
841 | if DJANGO_LT_1_7:
842 | key = '%s.%s' % (opts.app_label, opts.module_name)
843 | else:
844 | key = '%s.%s' % (opts.app_label, opts.model_name)
845 |
846 | return ROA_URL_OVERRIDES_DETAIL.get(key, func)(self, *args, **kwargs)
847 |
--------------------------------------------------------------------------------
/examples/django_roa_client/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | r"""
3 | Unit tests for django-roa.
4 |
5 | These tests assume that you've completed all the prerequisites for
6 | getting django-roa running in the default setup, to wit:
7 |
8 | 1. You have created the remote database: go to ``examples/django_roa_server``
9 | and run ``syncdb`` command with ``--noinput`` option in order to create
10 | a superuser named "roa_user" from fixtures:
11 |
12 | $ python manage.py syncdb --noinput
13 |
14 | 2. You have launched the project's server on port 8081 in order to test this
15 | suite against it with this command:
16 |
17 | $ python manage.py runserver 8081
18 |
19 | Now, you can go to ``examples/django_roa_client`` and run this command:
20 |
21 | $ python manage.py test django_roa_client
22 |
23 | It should return no error and you will be able to see logs from the test
24 | server which confirm that it works as expected: remote requests are done.
25 |
26 | Note: do not try to launch tests' projects if you put ``django_roa``
27 | application into your own project, otherwise it will fail. Django do not
28 | handle very well projects inside projects.
29 | """
30 | from datetime import time, date, datetime
31 |
32 | from django.test import TestCase
33 | from django.conf import settings
34 | from django.test.client import Client
35 | from django.core.serializers import register_serializer
36 | from django.contrib.contenttypes.models import ContentType
37 |
38 | from restkit import Resource
39 | from django_roa.remoteauth.models import User, Message, Group, Permission
40 | from django_roa_client.models import RemotePage, RemotePageWithManyFields, \
41 | RemotePageWithBooleanFields, RemotePageWithRelations, \
42 | RemotePageWithCustomSlug, RemotePageWithOverriddenUrls, \
43 | RemotePageWithNamedRelations, RemotePageWithProxy, \
44 | RemotePageWithRelationsThrough, RemotePageWithCustomPrimaryKey, \
45 | RemotePageWithCustomPrimaryKeyCountOverridden
46 | from django_roa_client.forms import TestForm, RemotePageForm
47 | from django_roa.db.exceptions import ROAException
48 |
49 | ROA_FILTERS = getattr(settings, 'ROA_FILTERS', {})
50 |
51 | class ROATestCase(TestCase):
52 |
53 | def setUp(self):
54 | RemotePage.objects.all().delete()
55 | RemotePageWithCustomPrimaryKey.objects.all().delete()
56 |
57 | def tearDown(self):
58 | RemotePage.objects.all().delete()
59 | RemotePageWithCustomPrimaryKey.objects.all().delete()
60 |
61 | class ROAUserTestCase(ROATestCase):
62 |
63 | def setUp(self):
64 | super(ROAUserTestCase, self).setUp()
65 | User.objects.all().delete()
66 | self.user = User.objects.create_superuser('admin', 'admin@example.org', 'admin')
67 |
68 | def tearDown(self):
69 | super(ROAUserTestCase, self).tearDown()
70 | User.objects.all().delete()
71 | Group.objects.all().delete()
72 | Permission.objects.all().delete()
73 | if Message:
74 | Message.objects.all().delete()
75 |
76 |
77 | class ROAInitializationTests(ROATestCase):
78 |
79 | def test_inheritance(self):
80 | self.assertEqual(str(RemotePage.objects.__class__), "")
81 | self.assertEqual(str(RemotePage.__class__), "")
82 |
83 |
84 | class ROABaseTests(ROATestCase):
85 |
86 | def test_basic_crud(self):
87 | page = RemotePage.objects.create(title=u'A first remote page')
88 | self.assertEqual(repr(page), '')
89 | page.title = u'Another title'
90 | page.save()
91 | page = RemotePage.objects.get(title=u'Another title')
92 | self.assertEqual(page.title, u'Another title')
93 | pages = RemotePage.objects.all()
94 | self.assertEqual(repr(pages), '[]')
95 | page.delete()
96 | self.assertEqual(repr(RemotePage.objects.all()), '[]')
97 | # test custom mapping of arguments too
98 | self.assertEqual(RemotePage.objects.count(), 0)
99 |
100 | def test_complex_crud(self):
101 | page = RemotePage.objects.create(title=u'')
102 | self.assertEqual(page.title, u'')
103 | page.title = u'A temporary title'
104 | page.save()
105 | page = RemotePage.objects.get(title=u'A temporary title')
106 | self.assertEqual(page.title, u'A temporary title')
107 | self.assertEqual(RemotePage.objects.count(), 1)
108 | page.title = u''
109 | page.save()
110 | page = RemotePage.objects.all()[0]
111 | self.assertEqual(page.title, u'')
112 | page.delete()
113 | self.assertEqual(repr(RemotePage.objects.all()), '[]')
114 | self.assertEqual(RemotePage.objects.count(), 0)
115 |
116 |
117 | class ROAUnicodeTests(ROATestCase):
118 |
119 | def test_remotepage(self):
120 | emilie = RemotePage.objects.create(title=u"Émilie")
121 | self.assertEqual(emilie.title, u'Émilie')
122 | emilie = RemotePage.objects.get(title=u"Émilie")
123 | self.assertEqual(emilie.title, u'Émilie')
124 | amelie = emilie
125 | amelie.title = u'Amélie'
126 | amelie.save()
127 | self.assertEqual(amelie.title, u'Amélie')
128 | amelie = RemotePage.objects.get(title=u"Amélie")
129 | self.assertEqual(amelie.title, u'Amélie')
130 | amelie.delete()
131 |
132 |
133 | class ROAFieldsTests(ROATestCase):
134 |
135 | def tearDown(self):
136 | super(ROAFieldsTests, self).tearDown()
137 | RemotePageWithManyFields.objects.all().delete()
138 | RemotePageWithBooleanFields.objects.all().delete()
139 |
140 | def test_empty_values(self):
141 | default_page = RemotePageWithManyFields.objects.create()
142 | self.assertEqual(default_page.id, 1)
143 | self.assertEqual(default_page.char_field, None)
144 | self.assertEqual(default_page.date_field, None)
145 | self.assertEqual(default_page.datetime_field, None)
146 | self.assertEqual(default_page.decimal_field, None)
147 | self.assertEqual(default_page.email_field, None)
148 | self.assertEqual(default_page.filepath_field, None)
149 | self.assertEqual(default_page.float_field, None)
150 | self.assertEqual(default_page.integer_field, None)
151 | self.assertEqual(default_page.ipaddress_field, None)
152 | self.assertEqual(default_page.positiveinteger_field, None)
153 | self.assertEqual(default_page.positivesmallinteger_field, None)
154 | self.assertEqual(default_page.slug_field, None)
155 | self.assertEqual(default_page.smallinteger_field, None)
156 | self.assertEqual(default_page.text_field, None)
157 | self.assertEqual(default_page.time_field, None)
158 | self.assertEqual(default_page.url_field, None)
159 | self.assertEqual(default_page.xml_field, None)
160 | self.assertEqual(repr(default_page.file_field), '')
161 | self.assertEqual(repr(default_page.image_field), '')
162 | retrieved_default_page = RemotePageWithManyFields.objects.get(id=default_page.id)
163 | self.assertEqual(default_page.id, retrieved_default_page.id)
164 | self.assertEqual(default_page.char_field, retrieved_default_page.char_field)
165 | self.assertEqual(default_page.date_field, retrieved_default_page.date_field)
166 | self.assertEqual(default_page.datetime_field, retrieved_default_page.datetime_field)
167 | self.assertEqual(default_page.decimal_field, retrieved_default_page.decimal_field)
168 | self.assertEqual(default_page.email_field, retrieved_default_page.email_field)
169 | self.assertEqual(default_page.filepath_field, retrieved_default_page.filepath_field)
170 | self.assertEqual(default_page.float_field, retrieved_default_page.float_field)
171 | self.assertEqual(default_page.integer_field, retrieved_default_page.integer_field)
172 | self.assertEqual(default_page.ipaddress_field, retrieved_default_page.ipaddress_field)
173 | self.assertEqual(default_page.positiveinteger_field, retrieved_default_page.positiveinteger_field)
174 | self.assertEqual(default_page.positivesmallinteger_field, retrieved_default_page.positivesmallinteger_field)
175 | self.assertEqual(default_page.slug_field, retrieved_default_page.slug_field)
176 | self.assertEqual(default_page.smallinteger_field, retrieved_default_page.smallinteger_field)
177 | self.assertEqual(default_page.text_field, retrieved_default_page.text_field)
178 | self.assertEqual(default_page.time_field, retrieved_default_page.time_field)
179 | self.assertEqual(default_page.url_field, retrieved_default_page.url_field)
180 | self.assertEqual(default_page.xml_field, retrieved_default_page.xml_field)
181 | self.assertEqual(repr(default_page.file_field), repr(retrieved_default_page.file_field))
182 | self.assertEqual(repr(default_page.image_field), repr(retrieved_default_page.image_field))
183 | default_page.delete()
184 |
185 | def test_empty_boolean_values(self):
186 | boolean_page = RemotePageWithBooleanFields.objects.create(boolean_field=True)
187 | self.assertEqual(boolean_page.id, 1)
188 | self.assertEqual(boolean_page.boolean_field, True)
189 | self.assertEqual(boolean_page.null_boolean_field, None)
190 | retrieved_boolean_page = RemotePageWithBooleanFields.objects.get(id=boolean_page.id)
191 | self.assertEqual(boolean_page.id, retrieved_boolean_page.id)
192 | self.assertEqual(boolean_page.boolean_field, retrieved_boolean_page.boolean_field)
193 | self.assertEqual(boolean_page.null_boolean_field, retrieved_boolean_page.null_boolean_field)
194 | boolean_page.delete()
195 |
196 | def test_boolean_field(self):
197 | page = RemotePageWithBooleanFields.objects.create(boolean_field=True)
198 | self.assertEqual(page.boolean_field, True)
199 | page = RemotePageWithBooleanFields.objects.get(id=page.id)
200 | self.assertEqual(page.boolean_field, True)
201 | page.boolean_field = False
202 | page.save()
203 | page = RemotePageWithBooleanFields.objects.get(id=page.id)
204 | self.assertEqual(page.boolean_field, False)
205 | page.delete()
206 |
207 | def test_char_field(self):
208 | page = RemotePageWithManyFields.objects.create(char_field=u'foo')
209 | self.assertEqual(page.char_field, u'foo')
210 | page = RemotePageWithManyFields.objects.get(id=page.id)
211 | self.assertEqual(page.char_field, u'foo')
212 | page.char_field = u'bar'
213 | page.save()
214 | page = RemotePageWithManyFields.objects.get(id=page.id)
215 | self.assertEqual(page.char_field, u'bar')
216 | page.delete()
217 |
218 | def test_date_field(self):
219 | page = RemotePageWithManyFields.objects.create(date_field=date(2008, 12, 24))
220 | self.assertEqual(repr(page.date_field), 'datetime.date(2008, 12, 24)')
221 | page = RemotePageWithManyFields.objects.get(id=page.id)
222 | self.assertEqual(repr(page.date_field), 'datetime.date(2008, 12, 24)')
223 | page.date_field = date(2008, 12, 25)
224 | page.save()
225 | page = RemotePageWithManyFields.objects.get(id=page.id)
226 | self.assertEqual(repr(page.date_field), 'datetime.date(2008, 12, 25)')
227 | page.delete()
228 |
229 | def test_datetime_field(self):
230 | page = RemotePageWithManyFields.objects.create(datetime_field=datetime(2008, 12, 24, 11, 53, 57))
231 | self.assertEqual(repr(page.datetime_field), 'datetime.datetime(2008, 12, 24, 11, 53, 57)')
232 | page = RemotePageWithManyFields.objects.get(id=page.id)
233 | self.assertEqual(repr(page.datetime_field), 'datetime.datetime(2008, 12, 24, 11, 53, 57)')
234 | page.datetime_field = datetime(2008, 12, 25, 13, 20)
235 | page.save()
236 | page = RemotePageWithManyFields.objects.get(id=page.id)
237 | self.assertEqual(repr(page.datetime_field), 'datetime.datetime(2008, 12, 25, 13, 20)')
238 | page.delete()
239 |
240 | def test_decimal_field(self):
241 | page = RemotePageWithManyFields.objects.create(decimal_field=1.55)
242 | self.assertEqual(page.decimal_field, 1.55)
243 | page = RemotePageWithManyFields.objects.get(id=page.id)
244 | self.assertEqual(repr(page.decimal_field), "Decimal('1.55')")
245 | page.decimal_field = 20.09
246 | page.save()
247 | page = RemotePageWithManyFields.objects.get(id=page.id)
248 | self.assertEqual(repr(page.decimal_field), "Decimal('20.09')")
249 | page.delete()
250 |
251 | def test_email_field(self):
252 | page = RemotePageWithManyFields.objects.create(email_field=u'test@example.com')
253 | self.assertEqual(page.email_field, u'test@example.com')
254 | page = RemotePageWithManyFields.objects.get(id=page.id)
255 | self.assertEqual(page.email_field, u'test@example.com')
256 | page.email_field = u'test@example.org'
257 | page.save()
258 | page = RemotePageWithManyFields.objects.get(id=page.id)
259 | self.assertEqual(page.email_field, u'test@example.org')
260 | page.delete()
261 |
262 | def test_filepath_field(self):
263 | page = RemotePageWithManyFields.objects.create(filepath_field=u'/foo/bar.zip')
264 | self.assertEqual(page.filepath_field, u'/foo/bar.zip')
265 | page = RemotePageWithManyFields.objects.get(id=page.id)
266 | self.assertEqual(page.filepath_field, u'/foo/bar.zip')
267 | page.filepath_field = u'/foo/bar/baz.tar.gz'
268 | page.save()
269 | page = RemotePageWithManyFields.objects.get(id=page.id)
270 | self.assertEqual(page.filepath_field, u'/foo/bar/baz.tar.gz')
271 | page.delete()
272 |
273 | def test_float_field(self):
274 | page = RemotePageWithManyFields.objects.create(float_field=1.55)
275 | self.assertEqual(page.float_field, 1.55)
276 | page = RemotePageWithManyFields.objects.get(id=page.id)
277 | self.assertEqual(page.float_field, 1.55)
278 | page.float_field = 20.09
279 | page.save()
280 | page = RemotePageWithManyFields.objects.get(id=page.id)
281 | self.assertEqual(page.float_field, 20.09)
282 | page.delete()
283 |
284 | def test_integer_field(self):
285 | page = RemotePageWithManyFields.objects.create(integer_field=155)
286 | self.assertEqual(page.integer_field, 155)
287 | page = RemotePageWithManyFields.objects.get(id=page.id)
288 | self.assertEqual(page.integer_field, 155)
289 | page.integer_field = 2009
290 | page.save()
291 | page = RemotePageWithManyFields.objects.get(id=page.id)
292 | self.assertEqual(page.integer_field, 2009)
293 | page.delete()
294 |
295 | def test_nullboolean_field(self):
296 | page = RemotePageWithBooleanFields.objects.create(null_boolean_field=True)
297 | self.assertEqual(page.null_boolean_field, True)
298 | page = RemotePageWithBooleanFields.objects.get(id=page.id)
299 | self.assertEqual(page.null_boolean_field, True)
300 | page.null_boolean_field = False
301 | page.save()
302 | page = RemotePageWithBooleanFields.objects.get(id=page.id)
303 | self.assertEqual(page.null_boolean_field, False)
304 | page.delete()
305 |
306 | def test_slug_field(self):
307 | page = RemotePageWithManyFields.objects.create(slug_field=u'foo-bar')
308 | self.assertEqual(page.slug_field, u'foo-bar')
309 | page = RemotePageWithManyFields.objects.get(id=page.id)
310 | self.assertEqual(page.slug_field, u'foo-bar')
311 | page.slug_field = u'bar-baz'
312 | page.save()
313 | page = RemotePageWithManyFields.objects.get(id=page.id)
314 | self.assertEqual(page.slug_field, u'bar-baz')
315 | page.delete()
316 |
317 | def test_text_field(self):
318 | page = RemotePageWithManyFields.objects.create(text_field=u'foo bar')
319 | self.assertEqual(page.text_field, u'foo bar')
320 | page = RemotePageWithManyFields.objects.get(id=page.id)
321 | self.assertEqual(page.text_field, u'foo bar')
322 | page.text_field = u'foo bar\nbaz'
323 | page.save()
324 | page = RemotePageWithManyFields.objects.get(id=page.id)
325 | self.assertEqual(page.text_field, u'foo bar\nbaz')
326 | page.delete()
327 |
328 | def test_time_field(self):
329 | page = RemotePageWithManyFields.objects.create(time_field=time(3, 51, 28))
330 | self.assertEqual(repr(page.time_field), 'datetime.time(3, 51, 28)')
331 | page = RemotePageWithManyFields.objects.get(id=page.id)
332 | self.assertEqual(repr(page.time_field), 'datetime.time(3, 51, 28)')
333 | page.time_field = time(11, 20, 53)
334 | page.save()
335 | page = RemotePageWithManyFields.objects.get(id=page.id)
336 | self.assertEqual(repr(page.time_field), 'datetime.time(11, 20, 53)')
337 | page.delete()
338 |
339 | def test_url_field(self):
340 | page = RemotePageWithManyFields.objects.create(url_field=u'http://example.com')
341 | self.assertEqual(page.url_field, u'http://example.com')
342 | page = RemotePageWithManyFields.objects.get(id=page.id)
343 | self.assertEqual(page.url_field, u'http://example.com')
344 | page.url_field = u'http://example.org'
345 | page.save()
346 | page = RemotePageWithManyFields.objects.get(id=page.id)
347 | self.assertEqual(page.url_field, u'http://example.org')
348 | page.delete()
349 |
350 |
351 | class ROARelationsTests(ROATestCase):
352 |
353 | def tearDown(self):
354 | super(ROARelationsTests, self).tearDown()
355 | RemotePageWithManyFields.objects.all().delete()
356 | RemotePageWithRelations.objects.all().delete()
357 | RemotePageWithNamedRelations.objects.all().delete()
358 |
359 | def test_empty_relation(self):
360 | relations_page = RemotePageWithRelations.objects.create()
361 | self.assertEqual(relations_page.id, 1)
362 | self.assertEqual(relations_page.title, '')
363 | self.assertEqual(relations_page.remote_page, None)
364 | retrieved_relations_page = RemotePageWithRelations.objects.get(id=relations_page.id)
365 | self.assertEqual(relations_page.id, retrieved_relations_page.id)
366 | self.assertEqual(relations_page.title, retrieved_relations_page.title)
367 | self.assertEqual(relations_page.remote_page, retrieved_relations_page.remote_page)
368 | relations_page.delete()
369 |
370 | def test_foreignkey_relation(self):
371 | remote_page = RemotePage.objects.create(title=u'A remote page')
372 | another_remote_page = RemotePage.objects.create(title=u'Another remote page')
373 | relations_page = RemotePageWithRelations.objects.create(remote_page=remote_page)
374 | self.assertEqual(repr(relations_page.remote_page), '')
375 | relations_page = RemotePageWithRelations.objects.get(id=relations_page.id)
376 | self.assertEqual(repr(relations_page.remote_page), '')
377 | self.assertEqual(repr(remote_page.remotepagewithrelations_set.all()), '[]')
378 | relations_page.remote_page = another_remote_page
379 | relations_page.save()
380 | relations_page = RemotePageWithRelations.objects.get(id=relations_page.id)
381 | self.assertEqual(repr(relations_page.remote_page), '')
382 | relations_page.delete()
383 | another_remote_page.delete()
384 | remote_page.delete()
385 |
386 | def test_manytomany_relation(self):
387 | remote_page = RemotePageWithManyFields.objects.create(char_field=u'A remote page')
388 | another_remote_page = RemotePageWithManyFields.objects.create(char_field=u'Another remote page')
389 | relations_page = RemotePageWithRelations.objects.create(title=u'A remote relation page')
390 | relations_page_through = RemotePageWithRelationsThrough.objects.create(title=u'A remote relation page through',
391 | remote_page_with_relations=relations_page,
392 | remote_page_with_many_fields=remote_page)
393 | self.assertEqual(repr(relations_page.remote_page_fields.all()), '[]')
394 | relations_page = RemotePageWithRelations.objects.get(id=relations_page.id)
395 | self.assertEqual(repr(relations_page.remote_page_fields.all()), '[]')
396 | another_relations_page_through = RemotePageWithRelationsThrough.objects.create(title=u'Another remote relation page through',
397 | remote_page_with_relations=relations_page,
398 | remote_page_with_many_fields=another_remote_page)
399 | relations_page = RemotePageWithRelations.objects.get(id=relations_page.id)
400 | self.assertEqual(repr(relations_page.remote_page_fields.all()), '[, ]')
401 | self.assertEqual(repr(remote_page.remotepagewithrelations_set.all()), '[]')
402 | relations_page_through.delete()
403 | self.assertEqual(repr(relations_page.remote_page_fields.all()), '[]')
404 | another_relations_page_through.delete()
405 | self.assertEqual(repr(relations_page.remote_page_fields.all()), '[]')
406 | relations_page.delete()
407 | another_remote_page.delete()
408 | remote_page.delete()
409 |
410 | def test_named_relation(self):
411 | remote_page = RemotePage.objects.create(title=u'A remote page')
412 | another_remote_page = RemotePage.objects.create(title=u'Another remote page')
413 | named_relations_page = RemotePageWithNamedRelations.objects.create(first_page=remote_page,
414 | last_page=another_remote_page)
415 | self.assertEqual(repr(named_relations_page.first_page), '')
416 | named_relations_page = RemotePageWithNamedRelations.objects.get(id=named_relations_page.id)
417 | self.assertEqual(repr(named_relations_page.first_page), '')
418 | self.assertEqual(repr(remote_page.from_first.all()), '[]')
419 | self.assertEqual(repr(another_remote_page.from_last.all()), '[]')
420 | named_relations_page.first_page = another_remote_page
421 | named_relations_page.save()
422 | named_relations_page = RemotePageWithNamedRelations.objects.get(id=named_relations_page.id)
423 | self.assertEqual(repr(named_relations_page.first_page), '')
424 | named_relations_page.delete()
425 | another_remote_page.delete()
426 | remote_page.delete()
427 |
428 | def test_proxy_relation(self):
429 | remote_page = RemotePage.objects.create(title=u'A remote page')
430 | proxy_remote_page = RemotePageWithProxy.objects.create(title=u'A proxy remote page')
431 | self.assertEqual(repr(remote_page), '')
432 | self.assertEqual(repr(proxy_remote_page), '')
433 | self.assertEqual(repr(RemotePage.objects.all()), '[, ]')
434 | self.assertEqual(repr(RemotePageWithProxy.objects.all()), '[, ]')
435 | proxy_remote_page.title = u'A modified proxy remote page'
436 | proxy_remote_page.save()
437 | proxy_remote_page = RemotePageWithProxy.objects.get(id=2)
438 | self.assertEqual(repr(proxy_remote_page), '')
439 | self.assertEqual(repr(RemotePage.objects.all()[1]), '')
440 | proxy_remote_page.delete()
441 | remote_page.delete()
442 |
443 |
444 | class ROAQuerysetTests(ROATestCase):
445 |
446 | def setUp(self):
447 | super(ROAQuerysetTests, self).setUp()
448 | self.remote_page1 = RemotePage.objects.create(title='A remote page')
449 | self.remote_page2 = RemotePage.objects.create(title='Another remote page')
450 | self.remote_page3 = RemotePage.objects.create(title='Yet another remote page')
451 | self.remote_page4 = RemotePage.objects.create(title='Still another remote page')
452 | self.remote_page_with_custom_primary_key1 = RemotePageWithCustomPrimaryKey.objects.create(title=u'Remote test page with custom primary')
453 | self.remote_page_with_custom_primary_key2 = RemotePageWithCustomPrimaryKey.objects.create(title=u'Another remote test page with custom primary')
454 |
455 | def test_getorcreate(self):
456 | self.assertEqual(repr(RemotePage.objects.get_or_create(title='A remote page')), '(, False)')
457 | remote_page, created = RemotePage.objects.get_or_create(title='A created remote page')
458 | self.assertEqual(created, True)
459 |
460 | def test_latest(self):
461 | self.assertEqual(repr(RemotePage.objects.latest('id')), '')
462 | self.assertEqual(repr(RemotePage.objects.latest('title')), '')
463 |
464 | def test_filtering(self):
465 | self.assertEqual(repr(RemotePage.objects.exclude(id=2)), '[, , ]')
466 | self.assertEqual(repr(RemotePage.objects.filter(title__iexact='ANOTHER remote page')), '[]')
467 | self.assertEqual(repr(RemotePage.objects.filter(title__contains='another')), '[, , ]')
468 |
469 | def test_ordering(self):
470 | self.assertEqual(repr(RemotePage.objects.order_by('title')), '[, , , ]')
471 | self.assertEqual(repr(RemotePage.objects.order_by('-title', '-id')), '[, , , ]')
472 |
473 | def test_slicing(self):
474 | self.assertEqual(repr(RemotePage.objects.all()[1:3]), '[, ]')
475 | self.assertEqual(repr(RemotePage.objects.all()[0]), '')
476 |
477 | def test_extra(self):
478 | self.assertEqual(bool(RemotePage.objects.all().extra(select={'a': 1}).values('a').order_by()), True)
479 | RemotePage.objects.all().delete()
480 | self.assertEqual(bool(RemotePage.objects.all().extra(select={'a': 1}).values('a').order_by()), False)
481 |
482 | def test_combined(self):
483 | self.assertEqual(repr(RemotePage.objects.exclude(title__contains='yet').order_by('title', '-id')[:2]), '[, ]')
484 |
485 | def test_get(self):
486 | # test get by pk, id directly
487 | self.assertEqual(repr(RemotePage.objects.get(id=1)), '')
488 | self.assertEqual(repr(RemotePage.objects.get(pk=2)), '')
489 | # test get by pk, id with exact filter
490 | self.assertEqual(repr(RemotePage.objects.get(id__exact=3)), '')
491 | self.assertEqual(repr(RemotePage.objects.get(pk__exact=4)), '')
492 |
493 | # test get by custom primary key attribute name
494 | self.assertEqual(repr(RemotePageWithCustomPrimaryKey.objects.get(auto_field=1)), '')
495 | self.assertEqual(repr(RemotePageWithCustomPrimaryKey.objects.get(auto_field=2)), '')
496 | with self.assertRaisesRegexp(ROAException, 'Not Found'):
497 | RemotePageWithCustomPrimaryKey.objects.get(auto_field=999)
498 |
499 | # test get by custom primary key with exact filter
500 | self.assertEqual(repr(RemotePageWithCustomPrimaryKey.objects.get(auto_field__exact=1)), '')
501 | self.assertEqual(repr(RemotePageWithCustomPrimaryKey.objects.get(auto_field__exact=2)), '')
502 | with self.assertRaisesRegexp(ROAException, 'Not Found'):
503 | RemotePageWithCustomPrimaryKey.objects.get(auto_field__exact=999)
504 |
505 | # test get by multiple attributes
506 | self.assertEqual(repr(RemotePageWithCustomPrimaryKey.objects.get(title='Another remote test page with custom primary', auto_field=2)), '')
507 | self.assertEqual(repr(RemotePageWithCustomPrimaryKey.objects.get(title__exact='Another remote test page with custom primary', auto_field__exact=2)), '')
508 | with self.assertRaisesRegexp(ROAException, 'Not Found'):
509 | RemotePageWithCustomPrimaryKey.objects.get(title__exact='Another remote test page with custom primary', auto_field__exact=999)
510 |
511 | def test_count(self):
512 | self.assertEqual(RemotePageWithCustomPrimaryKey.objects.count(), 2)
513 | RemotePageWithCustomPrimaryKey.objects.all().delete()
514 | self.assertEqual(RemotePageWithCustomPrimaryKey.objects.count(), 0)
515 |
516 | RemotePageWithCustomPrimaryKey.objects.create(title=u'Remote test page with custom primary')
517 | self.assertEqual(RemotePageWithCustomPrimaryKeyCountOverridden.objects.count(), 0)
518 |
519 | class ROAAdminTests(ROAUserTestCase):
520 |
521 | def test_admin_views(self):
522 | remote_page1 = RemotePage.objects.create(title='A remote page')
523 | remote_page2 = RemotePage.objects.create(title='Another remote page')
524 | remote_page3 = RemotePage.objects.create(title='Yet another remote page')
525 | remote_page4 = RemotePage.objects.create(title='Still another remote page')
526 | bob = User.objects.create_superuser(username=u'bob', password=u'secret', email=u'bob@example.com')
527 | bob = User.objects.get(username=u'bob')
528 | self.assertEqual(bob.is_superuser, True)
529 | c = Client()
530 | response = c.login(username=u'bob', password=u'secret')
531 | self.assertEqual(response, True)
532 | response = c.get('/admin/')
533 | # ._wrapped necessary because we compare string, comparison should
534 | # work with User.objects.get(username="bob") but slower...
535 | self.assertEqual(repr(response.context[-1]["user"]._wrapped), '')
536 | response = c.get('/admin/django_roa_client/remotepage/')
537 | self.assertEqual(response.status_code, 200)
538 | self.assertEqual(repr(response.context[-1]["cl"].result_list), '[, ]')
539 | self.assertEqual(response.context[-1]["cl"].result_count, 4)
540 |
541 |
542 | class ROAFormsTests(ROATestCase):
543 |
544 | def test_form_validation(self):
545 | form = TestForm()
546 | remote_page1 = RemotePage.objects.create(title='A remote page')
547 | self.assertEqual(form.is_valid(), False)
548 | form = TestForm(data={u'test_field': u'Test data', u'remote_page': remote_page1.id})
549 | self.assertEqual(form.is_valid(), True)
550 |
551 | def test_modelform_validation(self):
552 | form = RemotePageForm()
553 | self.assertEqual(form.is_valid(), False)
554 | form = RemotePageForm(data={u'title': u'Test data'})
555 | self.assertEqual(form.is_valid(), True)
556 | remote_page = form.save()
557 | self.assertEqual(repr(remote_page), '')
558 |
559 | def test_modelform_rendering(self):
560 | c = Client()
561 | remote_page1 = RemotePage.objects.create(title='A remote page')
562 | response = c.get('/')
563 | self.assertEqual('' in response.content, True)
564 |
565 |
566 | class ROARemoteAuthTests(ROAUserTestCase):
567 |
568 | def test_remote_users(self):
569 | self.assertEqual(repr(User.objects.all()), '[]')
570 | alice = User.objects.create_user(username=u'alice', password=u'secret', email=u'alice@example.com')
571 | self.assertEqual(alice.is_superuser, False)
572 | self.assertEqual(repr(User.objects.all()), '[, ]')
573 | self.assertEqual(alice.id, 2)
574 | if Message:
575 | self.assertEqual(repr(Message.objects.all()), '[]')
576 | message = Message.objects.create(user=alice, message=u'Test message')
577 | self.assertEqual(message.message, u'Test message')
578 | self.assertEqual(repr(message.user), '')
579 | self.assertEqual(repr(Message.objects.all()), '[]')
580 | self.assertEqual(repr(alice.message_set.all()), '[]')
581 |
582 | def test_select_related(self):
583 | # Not supported, we just verify that it doesn't break anything
584 | if Message:
585 | alice = User.objects.create_user(username=u'alice', password=u'secret', email=u'alice@example.com')
586 | message = Message.objects.create(user=alice, message=u'Test message')
587 | self.assertEqual(repr(Message.objects.all().select_related()), '[]')
588 | self.assertEqual(repr(Message.objects.all().select_related('user')), '[]')
589 |
590 | def test_groups(self):
591 | bob = User.objects.create_superuser(username=u'bob', password=u'secret', email=u'bob@example.com')
592 | self.assertEqual(repr(Group.objects.all()), '[]')
593 | self.assertEqual(repr(bob.groups.all()), '[]')
594 | ct_group = ContentType.objects.get(name='group')
595 | group_permission = Permission.objects.create(name=u"Custom permission to group model",
596 | content_type=ct_group,
597 | codename=u"custom_group_permission")
598 | group = Group.objects.create(name=u"Custom group")
599 | #group.permissions.add(group_permission)
600 | #bob.groups.add(group)
601 | #self.assertEqual(repr(bob.groups.all()), '[]')
602 | #self.assertEqual(repr(bob.groups.all()[0].permissions.all()), '[]')
603 | self.assertEqual(repr(bob.groups.all()), '[]')
604 |
605 | def test_permissions(self):
606 | bob = User.objects.create_superuser(username=u'bob', password=u'secret', email=u'bob@example.com')
607 | self.assertEqual(repr(bob.user_permissions.all()), '[]')
608 | ct_user = ContentType.objects.get(name='user')
609 | user_permission = Permission.objects.create(name=u"Custom permission to user model",
610 | content_type=ct_user,
611 | codename=u"custom_user_permission")
612 | #bob.user_permissions.add(user_permission)
613 | #self.assertEqual(repr(bob.user_permissions.all()), '[]')
614 | self.assertEqual(repr(bob.user_permissions.all()), '[]')
615 | ct_group = ContentType.objects.get(name='group')
616 | group_permission = Permission.objects.create(name=u"Custom permission to group model",
617 | content_type=ct_group,
618 | codename=u"custom_group_permission")
619 | group = Group.objects.create(name=u"Custom group")
620 | #group.permissions.add(group_permission)
621 | #bob.groups.add(group)
622 | #self.assertEqual(bob.get_group_permissions(), set([u'remoteauth.custom_group_permission']))
623 | #self.assertEqual(bob.get_all_permissions(), set([u'remoteauth.custom_group_permission', u'remoteauth.custom_user_permission']))
624 | self.assertEqual(bob.get_group_permissions(), set([]))
625 | self.assertEqual(bob.get_all_permissions(), set([]))
626 |
627 |
628 | class ROAExceptionsTests(ROAUserTestCase):
629 |
630 | def test_roa_errors(self):
631 | """
632 | FIXME: Find a way to do the same test with unittests:
633 |
634 | > User.objects.create_user(username="alice", password="secret", email="alice@example.com")
635 | Traceback (most recent call last):
636 | ...
637 | ROAException: IntegrityError at /auth/user/: column username is not unique
638 | Request Method: POST
639 | Request URL: http://127.0.0.1:8081/auth/user/?format=django
640 | Exception Type: IntegrityError
641 | Exception Value: column username is not unique
642 | Exception Location: ..., line ...
643 | Status code: 500
644 | """
645 | User.objects.create_user(username="alice", password="secret", email="alice@example.com")
646 | #User.objects.create_user(username="alice", password="secret", email="alice@example.com")
647 |
648 |
649 | class ROASettingsTests(ROATestCase):
650 |
651 | def tearDown(self):
652 | super(ROASettingsTests, self).tearDown()
653 | RemotePageWithCustomSlug.objects.all().delete()
654 | RemotePageWithOverriddenUrls.objects.all().delete()
655 |
656 | def test_custom_args(self):
657 | settings.ROA_CUSTOM_ARGS = {'foo': 'bar'}
658 | self.assertEqual(RemotePage.objects.all()._as_url(), (u'http://127.0.0.1:8081/django_roa_server/remotepage/', {'foo': 'bar', 'format': 'django'}))
659 | settings.ROA_CUSTOM_ARGS = {}
660 |
661 | def test_custom_slug(self):
662 | page_custom = RemotePageWithCustomSlug.objects.create(title=u"Test custom page")
663 | self.assertEqual(page_custom.slug, u'test-custom-page')
664 | page_custom = RemotePageWithCustomSlug.objects.get(title=u"Test custom page")
665 | self.assertEqual(repr(page_custom), '')
666 | page_custom.delete()
667 |
668 | def test_roa_url_overrides(self):
669 | page_overridden = RemotePageWithOverriddenUrls.objects.create(title=u"Test overridden urls")
670 | self.assertEqual(page_overridden.slug, u'test-overridden-urls')
671 | page_overridden = RemotePageWithOverriddenUrls.objects.get(title=u"Test overridden urls")
672 | self.assertEqual(repr(page_overridden), '')
673 | self.assertEqual(RemotePageWithOverriddenUrls.objects.all()._as_url(), (u'http://127.0.0.1:8081/django_roa_server/remotepagewithoverriddenurls/', {'format': 'django'}))
674 |
675 | def test_custom_serializer(self):
676 | register_serializer('custom', 'examples.django_roa_client.serializers')
677 | initial_roa_format_setting = settings.ROA_FORMAT
678 | settings.ROA_FORMAT = 'custom'
679 | page = RemotePage.objects.create(title=u'A custom serialized page')
680 | self.assertEqual(repr(page), '')
681 | r = Resource('http://127.0.0.1:8081/django_roa_server/remotepage/', filters=ROA_FILTERS)
682 | response = r.get(**{'format': 'custom'})
683 | self.assertEqual(repr(response.body_string()), '\'\\n\\n \\n\'')
684 | self.assertEqual(len(RemotePage.objects.all()), 1)
685 | page = RemotePage.objects.get(id=page.id)
686 | self.assertEqual(repr(page), '')
687 | settings.ROA_FORMAT = initial_roa_format_setting
688 |
--------------------------------------------------------------------------------