Work on MemberCreateResource, cleanups, README updates, misc

This commit is contained in:
nikolark committed 2013-12-08 23:51:17 +01:00
1 parent 36dd948e8e
commit 63894a93ff
9 files changed
+89 -69

No files matched your search

+18 -18
View File
@@ -18,7 +18,7 @@ You can find the documentation at [dusken.readthedocs.org](http://dusken.readthe
cp settings-sample.py settings.py
# Edit database settings (sqlite3 is easiest to setup, but postgresql is recommended. See discussion below.)
cd ..
./manage.py syncdb
./manage.py syncdb --all
./manage.py runserver
@@ -31,30 +31,30 @@ Download latest version of Vagrant from [downloads.vagrantup.com](http://downloa
vagrant plugin install vagrant-salt
vagrant up
vagrant ssh
## Development tasks
# Reset DB
python manage.py reset_db --router=default && python manage.py syncdb --all
## Test coverage
pip install coverage
coverage run --source='dusken' manage.py test dusken
coverage report -m
## Generate Fixtures for Test
# Generate Fixtures for Test
python manage.py dumpdata dusken.<model> -n --indent=4 > dusken/fixtures/<model>.json
## Database Diagram Generation
# Database Diagram Generation
sudo apt-get install graphviz libgraphviz-dev # system requirements
pip install pygraphviz django-extensions # python requirements
python manage.py graph_models -o diagramfile.png dusken auth
sudo apt-get install graphviz libgraphviz-dev
pip install pygraphviz django-extensions
python manage.py graph_models -o diagramfile.png dusken
## Design References
A similar data model might allready [exist](http://www.databaseanswers.org/data_models/generic_foundation/index.htm) [out](http://www.databaseanswers.org/data_models/organisations_and_people_and_transactions/index.htm) [there](http://www.databaseanswers.org/data_models/organisations_and_people/index.htm), [somewhere](http://www.databaseanswers.org/data_models/magazine_subscriptions/index.htm).
## Developer References
## Development references
* [Tastypie](https://django-tastypie.readthedocs.org/)
## SQLite3 vs PostgreSQL
## Database
### Design references
A similar data model might allready [exist](http://www.databaseanswers.org/data_models/generic_foundation/index.htm) [out](http://www.databaseanswers.org/data_models/organisations_and_people_and_transactions/index.htm) [there](http://www.databaseanswers.org/data_models/organisations_and_people/index.htm), [somewhere](http://www.databaseanswers.org/data_models/magazine_subscriptions/index.htm).
### SQLite3 vs PostgreSQL
* SQLite runs tests much faster
* SQLite does not check foreign key constraints out of the box [[1]](http://stackoverflow.com/questions/6745763/enable-integrity-checking-with-sqlite-in-django) [FIXED].
* SQLite is easier to set up.
+6 -4
View File
@@ -1,20 +1,22 @@
# Service design
ApiHook-model with the following attributes:
ServiceHook-model with the following attributes [DONE]:
* event
* callback-URL
* member
* (active)
Our own Tastypie BaseResource which triggers events based on:
* resource_name / type of object
* method used.
Use a queue for service tasks, f.ex rq (python library with redis backend)
Add logic to our Django Basemodel
Use a queue for service tasks:
* rq (python library with redis backend)
Take a look at Django signals:
Take a look at signals
* pre_save
* post_save
+4 -1
View File
@@ -30,9 +30,12 @@ admin.site.unregister(django.contrib.auth.models.Group)
admin.site.register(Address)
admin.site.register(Country)
admin.site.register(Division)
admin.site.register(Group)
admin.site.register(Institution)
admin.site.register(Member, MemberAdmin)
admin.site.register(MembershipType)
admin.site.register(MemberMeta)
admin.site.register(Membership)
admin.site.register(MembershipType)
admin.site.register(PlaceOfStudy)
admin.site.register(ServiceHook)
+22 -40
View File
@@ -9,12 +9,14 @@ from tastypie.exceptions import ImmediateHttpResponse, BadRequest
from tastypie.http import HttpForbidden, HttpNoContent, HttpResponse, HttpAccepted
from tastypie import fields
from tastypie.resources import ModelResource, ALL
from tastypie.validation import CleanedDataFormValidation
from dusken.api.groupsbymember import GroupsByMemberResource
from dusken.authentication import MyApiKeyAuthentication, ServiceAuthentication
from dusken.authorization import MyDjangoAuthorization
from dusken.models import *
from dusken.utils.api import generate_username, random_string
from dusken.forms import MemberCreateForm
class MemberResource(ModelResource):
@@ -57,28 +59,6 @@ class MemberResource(ModelResource):
resource = MemberCreateResource()
return resource.dispatch_list(request, **kwargs)
#def apply_filters(self, request, applicable_filters):
# # Extra filters are used for filtering by attributes in User class. Find out if there
# # are any, and if so, apply them.
# extra_filters = {}
# if 'username__exact' in applicable_filters:
# extra_filters['username'] = applicable_filters.pop('username__exact')
# if 'first_name__exact' in applicable_filters:
# extra_filters['first_name'] = applicable_filters.pop('first_name__exact')
# if 'last_name__exact' in applicable_filters:
# extra_filters['last_name'] = applicable_filters.pop('last_name__exact')
# if 'email__exact' in applicable_filters:
# extra_filters['email'] = applicable_filters.pop('email__exact')
# filtered = super(MemberResource, self).apply_filters(request, applicable_filters)
# for key,value in extra_filters.items():
# filtered = filter(lambda m: getattr(m, key) == value, filtered)
# return filtered
def hydrate(self, bundle):
"""
Catches PATCH requests and intercepts data.
@@ -105,6 +85,8 @@ class MemberResource(ModelResource):
def _update_address(self, address, new_address):
if 'street_address' in new_address:
address.street_address = new_address['street_address']
if 'street_address_two' in new_address:
address.street_address_two = new_address['street_address_two']
if 'city' in new_address:
address.city = new_address['city']
if 'postal_code' in new_address:
@@ -169,7 +151,7 @@ class MemberResource(ModelResource):
class MemberCreateResource(ModelResource):
# TODO
# - get the post request to return auth info
# - do not return fields in excludes list
# - Code 400 is triggered on validation error instead of 403 in tastypie.resource.ModelResource.save().
"""
This class provides the following endpoint:
(1) /api/v1/member/register/
@@ -189,27 +171,28 @@ class MemberCreateResource(ModelResource):
authorization = Authorization() # can do what they want with anything
always_return_data = True
excludes = [ 'date_joined', 'password', 'is_active', 'is_staff', 'is_superuser', 'last_login' ]
validation = CleanedDataFormValidation(form_class=MemberCreateForm)
def dehydrate(self, bundle):
# Do not return fields in excludes
# Ref: https://groups.google.com/forum/#!topic/django-tastypie/WE_d92Fkl-I/discussion
for field in getattr(self.Meta, 'excludes', []):
if field in bundle.data:
del bundle.data[field]
return bundle
def obj_create(self, bundle, request=None, **kwargs):
# TODO factor out validation http://django-tastypie.readthedocs.org/en/latest/validation.html
# email must be unique
email = bundle.data.get('email')
if(len(Member.objects.filter(email__iexact=email)) > 0):
raise ImmediateHttpResponse(HttpForbidden("E-mail '{0}' already exists".format(email)))
bundle.data['email'] = Member.objects.normalize_email(email)
# TODO factor out validation and cleaning of fields:
# * password
# * username
password = bundle.data.get('password')
if not password:
# generate a random password if it is not set
# generate a random password if it is not provided
bundle.data['password'] = Member.objects.make_random_password(length=32)
username = bundle.data.get('username')
if not username:
username = bundle.data['username'] = generate_username(bundle.data)
# FIXME unsafe
while(len(Member.objects.filter(username=username)) > 0):
username = bundle.data['username'] = generate_username(bundle.data)
if not bundle.data.get('username'):
# Simply use the provided email
bundle.data['username'] = bundle.data['email']
# When creating a new user, check if it already exists:
try:
@@ -217,7 +200,6 @@ class MemberCreateResource(ModelResource):
bundle.obj.set_password(password)
bundle.obj.save()
except IntegrityError as e:
print e
raise ImmediateHttpResponse(HttpForbidden('That username already exists'))
raise ImmediateHttpResponse(HttpForbidden("Database error: {0}".format(e)))
return bundle
+18
View File
@@ -0,0 +1,18 @@
from django import forms
from models import Member
class MemberCreateForm(forms.ModelForm):
# TODO
# custom validation of:
# - phonenumber (phonenumbers package, unique)
# - email (+unique)
# - legacy_id (unique)
#
# clean:
# - phonenumber
# - username
# - password
class Meta:
model = Member
+17 -4
View File
@@ -18,9 +18,9 @@ class Member(django.contrib.auth.models.AbstractUser):
username=self.username)
return u"{username}".format(username=self.username)
phone_number = models.CharField(max_length=30, unique=True, null=True, blank=True)
phone_number = models.CharField(max_length=30, null=True, blank=True)
date_of_birth = models.DateField(blank=True, null=True)
legacy_id = models.IntegerField(unique=True, null=True, blank=True)
legacy_id = models.IntegerField(null=True, blank=True)
address = models.OneToOneField('dusken.Address', null=True, blank=True)
place_of_study = models.ManyToManyField('dusken.PlaceOfStudy', null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
@@ -136,9 +136,9 @@ class Group(django.contrib.auth.models.Group):
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class VolunteerGroup(BaseModel):
class Division(BaseModel):
"""
Associations
Associations, comittee or similar
"""
def __unicode__(self):
return u"{}".format(self.name)
@@ -149,6 +149,19 @@ class VolunteerGroup(BaseModel):
parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
groups = models.ManyToManyField('dusken.Group', null=True, blank=True) # permissions
class ServiceHook(BaseModel):
"""
Events with callback_urls
"""
def __unicode__(self):
return u"{}".format(self.name)
event = models.CharField(max_length=255)
member = models.ForeignKey('dusken.Member')
is_active = models.BooleanField(default=True)
callback_url = models.TextField()
# SIGNALS #
###########
models.signals.post_save.connect(create_api_key, sender=Member)
+1
View File
@@ -118,6 +118,7 @@ INSTALLED_APPS = (
'dusken',
'tastypie',
'south',
'django_extensions',
'corsheaders',
)
+2 -1
View File
@@ -138,7 +138,8 @@ class MemberTest(MemberTestBase):
format = 'json',
data = data,
authentication=self.creds)
self.assertHttpForbidden(resp)
#self.assertHttpForbidden(resp) # FIXME see MemberCreateResource in member.py
self.assertHttpBadRequest(resp)
def test_patch_change_member(self):
"""
+1 -1
View File
@@ -6,7 +6,7 @@ if [ -f dusken.db ]; then
fi
echo 'Synchronizing database...'
python manage.py syncdb --noinput > /dev/null
python manage.py syncdb --noinput --all > /dev/null
echo 'Migrating database...'
python manage.py migrate > /dev/null