-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The model form are supported in the formapi and details #5
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,16 @@ | ||
from collections import defaultdict | ||
from decimal import Decimal | ||
import datetime | ||
import decimal | ||
import hmac | ||
import itertools | ||
import logging | ||
import urllib2 | ||
|
||
from collections import defaultdict | ||
from decimal import Decimal | ||
from hashlib import sha1 | ||
from json import dumps, loads, JSONEncoder | ||
import datetime | ||
|
||
from django.db.models.query import QuerySet, ValuesQuerySet | ||
from django.conf import settings | ||
from django.core import serializers | ||
from django.http import HttpResponse, Http404 | ||
|
@@ -16,9 +20,8 @@ | |
from django.utils.importlib import import_module | ||
from django.views.decorators.csrf import csrf_exempt | ||
from django.views.generic import FormView | ||
from django.db.models.query import QuerySet, ValuesQuerySet | ||
from django.utils.functional import curry, Promise | ||
import itertools | ||
|
||
from .models import APIKey | ||
|
||
LOG = logging.getLogger('formapi') | ||
|
@@ -83,11 +86,13 @@ class API(FormView): | |
template_name = 'formapi/api/form.html' | ||
signed_requests = True | ||
call_mapping = defaultdict(lambda: defaultdict(dict)) | ||
request_passed = False | ||
request_kwarg = 'request' | ||
|
||
@classmethod | ||
def register(cls, call_cls, namespace, name=None, version='beta'): | ||
call_name = name or call_cls.__name__ | ||
API.call_mapping[version][namespace][call_name] = call_cls | ||
cls.call_mapping[version][namespace][call_name] = call_cls | ||
|
||
@classonlymethod | ||
def as_view(cls, **initkwargs): | ||
|
@@ -96,18 +101,31 @@ def as_view(cls, **initkwargs): | |
|
||
def get_form_class(self): | ||
try: | ||
return API.call_mapping[self.version][self.namespace][self.call] | ||
return self.call_mapping[self.version][self.namespace][self.call] | ||
except KeyError: | ||
raise Http404 | ||
|
||
def get_form_kwargs(self): | ||
kwargs = super(API, self).get_form_kwargs() | ||
form_class = self.get_form_class() | ||
if getattr(form_class, 'request_passed', self.request_passed): | ||
request_kwarg = getattr(form_class, 'request_kwarg', self.request_kwarg) | ||
kwargs[request_kwarg] = self.request | ||
get_instance = getattr(form_class, 'get_instance', None) | ||
if get_instance: | ||
instance = get_instance(self.request) | ||
if instance: | ||
kwargs['instance'] = instance | ||
return kwargs | ||
|
||
def get_access_params(self): | ||
key = self.request.REQUEST.get('key') | ||
sign = self.request.REQUEST.get('sign') | ||
return key, sign | ||
|
||
def sign_ok(self, sign): | ||
pairs = ((field, self.request.REQUEST.get(field)) | ||
for field in sorted(self.get_form_class()().fields.keys())) | ||
for field in sorted(self.get_form_class()(**self.get_form_kwargs()).fields.keys())) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be cleaner to do this as |
||
filtered_pairs = itertools.ifilter(lambda x: x[1] is not None, pairs) | ||
query_string = '&'.join(('='.join(pair) for pair in filtered_pairs)) | ||
query_string = urllib2.quote(query_string.encode('utf-8')) | ||
|
@@ -157,7 +175,7 @@ def setup_log(self, log): | |
self.log = AddHeaderAdapter(log, {'header': self.get_log_header()}) | ||
|
||
def authorize(self): | ||
if getattr(self.get_form_class(), 'signed_requests', API.signed_requests): | ||
if getattr(self.get_form_class(), 'signed_requests', self.signed_requests): | ||
key, sign = self.get_access_params() | ||
### Check for not revoked api key | ||
try: | ||
|
@@ -173,7 +191,6 @@ def authorize(self): | |
def dispatch(self, request, *args, **kwargs): | ||
# Set up request | ||
self.request = request | ||
|
||
# Set up form class | ||
self.version = kwargs['version'] | ||
self.namespace = kwargs['namespace'] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,44 @@ | ||
from django.forms import forms | ||
from django import forms | ||
from django.core import serializers | ||
from django.forms.forms import NON_FIELD_ERRORS | ||
from django.shortcuts import get_object_or_404 | ||
|
||
|
||
class APICall(forms.Form): | ||
class BaseAPICall(object): | ||
|
||
request_passed = False | ||
request_kwarg = 'request' | ||
|
||
def add_error(self, error_msg): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even if this isn't very clean and I agree it should be removed we can't just remove this without deprecating it first. |
||
errors = self.non_field_errors() | ||
errors.append(error_msg) | ||
self._errors[forms.NON_FIELD_ERRORS] = errors | ||
|
||
def clean(self): | ||
for name, data in self.cleaned_data.iteritems(): | ||
setattr(self, name, data) | ||
return super(APICall, self).clean() | ||
self._errors[NON_FIELD_ERRORS] = errors | ||
|
||
def action(self, test): | ||
raise NotImplementedError('APIForms must implement action(self, test)') | ||
|
||
|
||
class APICall(forms.Form, BaseAPICall): | ||
|
||
instance_kwargs = None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't see where |
||
|
||
|
||
class APIModelCall(forms.ModelForm, BaseAPICall): | ||
|
||
instance_pk_param = '__instance_pk' | ||
pk_field = 'pk' | ||
|
||
@classmethod | ||
def get_instance(cls, request): | ||
instance_pk = request.REQUEST.get(cls.instance_pk_param, None) | ||
if instance_pk: | ||
model_class = cls._meta.model | ||
return get_object_or_404(model_class, **{cls.pk_field: cls.webiste_slug}) | ||
return None | ||
|
||
def serialize_obj(self, obj): | ||
return serializers.serialize('json', [obj])[1:-1] | ||
|
||
def action(self, test): | ||
obj = self.save() | ||
return self.serialize_obj(obj) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason why you're serializing the object in the form and not letting |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this method can get pretty complex by time. If we could break out the
get_instance
code into a_get_model_form_kwargs
that is run ifisinstance(form_class, APIModelCall)
isTrue
and then skip theget_attr
checks.