From abc7439f8ddcd1ec2540a664ce2f025907c1ce67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20=C5=BBurek?= Date: Tue, 3 Jan 2012 09:55:12 +0100 Subject: [PATCH 1/3] QueryMixin created + related mixins updates --- djangorestframework/mixins.py | 137 ++++++++++++++++++++++++---------- 1 file changed, 98 insertions(+), 39 deletions(-) diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py index b1a634a07..894208095 100644 --- a/djangorestframework/mixins.py +++ b/djangorestframework/mixins.py @@ -6,6 +6,7 @@ classes that can be added to a `View`. from django.contrib.auth.models import AnonymousUser from django.core.paginator import Paginator from django.db.models.fields.related import ForeignKey +from django.db.models.query import Q from django.http import HttpResponse from djangorestframework import status @@ -35,6 +36,82 @@ __all__ = ( ) +#### Base for *ModelMixins using URL arguments to filter out queryset or instances #### + +class QueryMixin(object): + """ Implements mechanisms used by other classes (like *ModelMixin group) to + define a query that represents Model instances the Mixin is working with. + + If a *ModelMixin is going to retrive an instance (or queryset) using args and kwargs + passed by as URL arguments, it should provied arguments to objects.get and objects.filter + methods wrapped in by `build_query` + + + """ + def build_query(self, *args, **kwargs): + + # This methods simply mimics the previous behaviour of the framework, where + # the following code was used few times in to retrive the instance: + # + # ------------------------------ + # if args: + # #If we have any none kwargs then assume the last represents the primrary key + # instance = model.objects.get(pk=args[-1], **kwargs) + # else: + # # Otherwise assume the kwargs uniquely identify the model + # instance = model.objects.get(**kwargs) + # ----------------------------- + # + # this block is now replaced with + # + # ------------- + # instance = model.objects.get(self.build_query(*args, **kwargs) + # ------------- + # + # which is more DRY + gives the simple possibility to provide + # *any* arguments in URL + # + + tmp = dict(kwargs) + if args: + # While this code simply follows the previous behaviour, we feel this + # is somehow strange to use 'pk' with any other query parameters... isn't it? + + # If we have any none kwargs then assume the last represents the primrary key + # Otherwise assume the kwargs uniquely identify the model + tmp.update({'pk': args[-1]}) + return Q(**tmp) + + + def get_instance_data(self, model, content, *args, **kwargs): + + tmp = dict(kwargs) + + for field in model._meta.fields: + if isinstance(field, ForeignKey) and tmp.has_key(field.name): + # translate 'related_field' kwargs into 'related_field_id' + tmp[field.name + '_id'] = tmp[field.name] + del tmp[field.name] + + all_kw_args = dict(content.items() + tmp.items()) + + if args: + all_kw_args.update({'pk': args[-1]}) + + return all_kw_args + + + # TODO: get_object and get_queryset methods should be implemented somehow. This will + # give a nice parallel to django class-based generic views. We're leaving this + # unimplementd at the moment. + + def get_object(self): + pass + + def get_queryset(self): + pass + + ########## Request Mixin ########## class RequestMixin(object): @@ -481,7 +558,7 @@ class InstanceMixin(object): ########## Model Mixins ########## -class ReadModelMixin(object): +class ReadModelMixin(QueryMixin): """ Behavior to read a `model` instance on GET requests """ @@ -489,22 +566,21 @@ class ReadModelMixin(object): model = self.resource.model try: - if args: - # If we have any none kwargs then assume the last represents the primrary key - self.model_instance = model.objects.get(pk=args[-1], **kwargs) - else: - # Otherwise assume the kwargs uniquely identify the model - filtered_keywords = kwargs.copy() - if BaseRenderer._FORMAT_QUERY_PARAM in filtered_keywords: - del filtered_keywords[BaseRenderer._FORMAT_QUERY_PARAM] - self.model_instance = model.objects.get(**filtered_keywords) + self.model_instance = model.objects.get(self.build_query(*args, **kwargs)) except model.DoesNotExist: raise ErrorResponse(status.HTTP_404_NOT_FOUND) return self.model_instance + def build_query(self, *args, **kwargs): + # Build query is overriden to filter the kwargs priori + # to use them as build_query argument + filtered_keywords = kwargs.copy() + if BaseRenderer._FORMAT_QUERY_PARAM in filtered_keywords: + del filtered_keywords[BaseRenderer._FORMAT_QUERY_PARAM] + return super(ReadModelMixin, self).build_query(*args, **filtered_keywords) -class CreateModelMixin(object): +class CreateModelMixin(QueryMixin): """ Behavior to create a `model` instance on POST requests """ @@ -515,11 +591,6 @@ class CreateModelMixin(object): content = dict(self.CONTENT) m2m_data = {} - for field in model._meta.fields: - if isinstance(field, ForeignKey) and kwargs.has_key(field.name): - # translate 'related_field' kwargs into 'related_field_id' - kwargs[field.name + '_id'] = kwargs[field.name] - del kwargs[field.name] for field in model._meta.many_to_many: if content.has_key(field.name): @@ -528,12 +599,8 @@ class CreateModelMixin(object): ) del content[field.name] - all_kw_args = dict(content.items() + kwargs.items()) - if args: - instance = model(pk=args[-1], **all_kw_args) - else: - instance = model(**all_kw_args) + instance = model(**self.get_instance_data(model, content, *args, **kwargs)) instance.save() for fieldname in m2m_data: @@ -555,7 +622,7 @@ class CreateModelMixin(object): return Response(status.HTTP_201_CREATED, instance, headers) -class UpdateModelMixin(object): +class UpdateModelMixin(QueryMixin): """ Behavior to update a `model` instance on PUT requests """ @@ -564,24 +631,21 @@ class UpdateModelMixin(object): # TODO: update on the url of a non-existing resource url doesn't work correctly at the moment - will end up with a new url try: - if args: - # If we have any none kwargs then assume the last represents the primary key - self.model_instance = model.objects.get(pk=args[-1], **kwargs) - else: - # Otherwise assume the kwargs uniquely identify the model - self.model_instance = model.objects.get(**kwargs) + self.model_instance = model.objects.get(self.build_query(*args, **kwargs)) for (key, val) in self.CONTENT.items(): setattr(self.model_instance, key, val) except model.DoesNotExist: - self.model_instance = model(**self.CONTENT) - self.model_instance.save() + self.model_instance = model(**self.get_instance_data(model, self.CONTENT, *args, **kwargs)) + # args + kwartgs were not provided... + # self.model_instance = model(**self.CONTENT) + # self.model_instance.save() self.model_instance.save() return self.model_instance -class DeleteModelMixin(object): +class DeleteModelMixin(QueryMixin): """ Behavior to delete a `model` instance on DELETE requests """ @@ -589,12 +653,7 @@ class DeleteModelMixin(object): model = self.resource.model try: - if args: - # If we have any none kwargs then assume the last represents the primrary key - instance = model.objects.get(pk=args[-1], **kwargs) - else: - # Otherwise assume the kwargs uniquely identify the model - instance = model.objects.get(**kwargs) + instance = model.objects.get(self.build_query(*args, **kwargs)) except model.DoesNotExist: raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {}) @@ -602,7 +661,7 @@ class DeleteModelMixin(object): return -class ListModelMixin(object): +class ListModelMixin(QueryMixin): """ Behavior to list a set of `model` instances on GET requests """ @@ -634,7 +693,7 @@ class ListModelMixin(object): if ordering: args = as_tuple(ordering) queryset = queryset.order_by(*args) - return queryset.filter(**kwargs) + return queryset.filter(self.build_query(**kwargs)) ########## Pagination Mixins ########## From 10adf4c31ab9430745c9c1f721f73e7fc3863d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20=C5=BBurek?= Date: Sun, 8 Jan 2012 23:08:00 +0100 Subject: [PATCH 2/3] QueryMixin class updates (comments + docs and the definition of get_instance_data method) --- djangorestframework/mixins.py | 78 +++++++++++++++-------------------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py index 894208095..2faf9b73a 100644 --- a/djangorestframework/mixins.py +++ b/djangorestframework/mixins.py @@ -46,44 +46,52 @@ class QueryMixin(object): passed by as URL arguments, it should provied arguments to objects.get and objects.filter methods wrapped in by `build_query` + If a *ModelMixin is going to create/update an instance get_instance_data handles the instance + data creation/preaparation. """ + def build_query(self, *args, **kwargs): - - # This methods simply mimics the previous behaviour of the framework, where - # the following code was used few times in to retrive the instance: - # - # ------------------------------ - # if args: - # #If we have any none kwargs then assume the last represents the primrary key - # instance = model.objects.get(pk=args[-1], **kwargs) - # else: - # # Otherwise assume the kwargs uniquely identify the model - # instance = model.objects.get(**kwargs) - # ----------------------------- - # - # this block is now replaced with - # - # ------------- - # instance = model.objects.get(self.build_query(*args, **kwargs) - # ------------- - # - # which is more DRY + gives the simple possibility to provide - # *any* arguments in URL - # + """ Returns django.db.models.Q object to be used for the objects retrival. + + Arguments: + - args: unnamed URL arguments + - kwargs: named URL arguments + + If a URL passes any arguments to the view being the QueryMixin subclass + build_query manages the arguments and provides the Q object that will be + used for the objects retrival with filter/get queryset methods. + + Technically, either args nor kwargs have to be provided, however the default + behaviour is to map all kwargs as the query constructors so that if this + method is not overriden only kwargs keys being model fields are valid. + + If args are provided, the last one (args[-1) is understood as instance pk. This + should be removed in the future, though. + + """ tmp = dict(kwargs) if args: - # While this code simply follows the previous behaviour, we feel this - # is somehow strange to use 'pk' with any other query parameters... isn't it? - # If we have any none kwargs then assume the last represents the primrary key # Otherwise assume the kwargs uniquely identify the model tmp.update({'pk': args[-1]}) return Q(**tmp) - def get_instance_data(self, model, content, *args, **kwargs): + def get_instance_data(self, model, content, **kwargs): + """ Returns the dict with the data for model instance creation/update query. + + Arguments: + - model: model class (django.db.models.Model subclass) to work with + - content: a dictionary with instance data + - kwargs: a dict of URL provided keyword arguments + + The create/update queries are created basicly with the contet provided + with POST/PUT HTML methods and kwargs passed in the URL. This methods simply merges + the URL data and the content preaparing the ready-to-use data dictionary. + + """ tmp = dict(kwargs) @@ -95,23 +103,9 @@ class QueryMixin(object): all_kw_args = dict(content.items() + tmp.items()) - if args: - all_kw_args.update({'pk': args[-1]}) - return all_kw_args - # TODO: get_object and get_queryset methods should be implemented somehow. This will - # give a nice parallel to django class-based generic views. We're leaving this - # unimplementd at the moment. - - def get_object(self): - pass - - def get_queryset(self): - pass - - ########## Request Mixin ########## class RequestMixin(object): @@ -637,10 +631,6 @@ class UpdateModelMixin(QueryMixin): setattr(self.model_instance, key, val) except model.DoesNotExist: self.model_instance = model(**self.get_instance_data(model, self.CONTENT, *args, **kwargs)) - # args + kwartgs were not provided... - # self.model_instance = model(**self.CONTENT) - # self.model_instance.save() - self.model_instance.save() return self.model_instance From 86b1495cad67322fd3be8feee0f87d93e3f81d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20=C5=BBurek?= Date: Tue, 10 Jan 2012 21:42:50 +0100 Subject: [PATCH 3/3] QueryMixin to ModelMixin rename. --- djangorestframework/mixins.py | 148 +++++++++++++++++----------------- 1 file changed, 73 insertions(+), 75 deletions(-) diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py index 2faf9b73a..f57760ecb 100644 --- a/djangorestframework/mixins.py +++ b/djangorestframework/mixins.py @@ -36,76 +36,6 @@ __all__ = ( ) -#### Base for *ModelMixins using URL arguments to filter out queryset or instances #### - -class QueryMixin(object): - """ Implements mechanisms used by other classes (like *ModelMixin group) to - define a query that represents Model instances the Mixin is working with. - - If a *ModelMixin is going to retrive an instance (or queryset) using args and kwargs - passed by as URL arguments, it should provied arguments to objects.get and objects.filter - methods wrapped in by `build_query` - - If a *ModelMixin is going to create/update an instance get_instance_data handles the instance - data creation/preaparation. - - """ - - def build_query(self, *args, **kwargs): - """ Returns django.db.models.Q object to be used for the objects retrival. - - Arguments: - - args: unnamed URL arguments - - kwargs: named URL arguments - - If a URL passes any arguments to the view being the QueryMixin subclass - build_query manages the arguments and provides the Q object that will be - used for the objects retrival with filter/get queryset methods. - - Technically, either args nor kwargs have to be provided, however the default - behaviour is to map all kwargs as the query constructors so that if this - method is not overriden only kwargs keys being model fields are valid. - - If args are provided, the last one (args[-1) is understood as instance pk. This - should be removed in the future, though. - - """ - - tmp = dict(kwargs) - if args: - # If we have any none kwargs then assume the last represents the primrary key - # Otherwise assume the kwargs uniquely identify the model - tmp.update({'pk': args[-1]}) - return Q(**tmp) - - - def get_instance_data(self, model, content, **kwargs): - """ Returns the dict with the data for model instance creation/update query. - - Arguments: - - model: model class (django.db.models.Model subclass) to work with - - content: a dictionary with instance data - - kwargs: a dict of URL provided keyword arguments - - The create/update queries are created basicly with the contet provided - with POST/PUT HTML methods and kwargs passed in the URL. This methods simply merges - the URL data and the content preaparing the ready-to-use data dictionary. - - """ - - tmp = dict(kwargs) - - for field in model._meta.fields: - if isinstance(field, ForeignKey) and tmp.has_key(field.name): - # translate 'related_field' kwargs into 'related_field_id' - tmp[field.name + '_id'] = tmp[field.name] - del tmp[field.name] - - all_kw_args = dict(content.items() + tmp.items()) - - return all_kw_args - - ########## Request Mixin ########## class RequestMixin(object): @@ -552,7 +482,75 @@ class InstanceMixin(object): ########## Model Mixins ########## -class ReadModelMixin(QueryMixin): +class ModelMixin(object): + """ Implements mechanisms used by other classes (like *ModelMixin group) to + define a query that represents Model instances the Mixin is working with. + + If a *ModelMixin is going to retrive an instance (or queryset) using args and kwargs + passed by as URL arguments, it should provied arguments to objects.get and objects.filter + methods wrapped in by `build_query` + + If a *ModelMixin is going to create/update an instance get_instance_data handles the instance + data creation/preaparation. + + """ + + def build_query(self, *args, **kwargs): + """ Returns django.db.models.Q object to be used for the objects retrival. + + Arguments: + - args: unnamed URL arguments + - kwargs: named URL arguments + + If a URL passes any arguments to the view being the QueryMixin subclass + build_query manages the arguments and provides the Q object that will be + used for the objects retrival with filter/get queryset methods. + + Technically, neither args nor kwargs have to be provided, however the default + behaviour is to map all kwargs as the query constructors so that if this + method is not overriden only kwargs keys being model fields are valid. + + If args are provided, the last one (args[-1) is understood as instance pk. This + should be removed in the future, though. + + """ + + tmp = dict(kwargs) + if args: + # If we have any none kwargs then assume the last represents the primrary key + # Otherwise assume the kwargs uniquely identify the model + tmp.update({'pk': args[-1]}) + return Q(**tmp) + + + def get_instance_data(self, model, content, **kwargs): + """ Returns the dict with the data for model instance creation/update query. + + Arguments: + - model: model class (django.db.models.Model subclass) to work with + - content: a dictionary with instance data + - kwargs: a dict of URL provided keyword arguments + + The create/update queries are created basicly with the contet provided + with POST/PUT HTML methods and kwargs passed in the URL. This methods simply merges + the URL data and the content preaparing the ready-to-use data dictionary. + + """ + + tmp = dict(kwargs) + + for field in model._meta.fields: + if isinstance(field, ForeignKey) and tmp.has_key(field.name): + # translate 'related_field' kwargs into 'related_field_id' + tmp[field.name + '_id'] = tmp[field.name] + del tmp[field.name] + + all_kw_args = dict(content.items() + tmp.items()) + + return all_kw_args + + +class ReadModelMixin(ModelMixin): """ Behavior to read a `model` instance on GET requests """ @@ -574,7 +572,7 @@ class ReadModelMixin(QueryMixin): del filtered_keywords[BaseRenderer._FORMAT_QUERY_PARAM] return super(ReadModelMixin, self).build_query(*args, **filtered_keywords) -class CreateModelMixin(QueryMixin): +class CreateModelMixin(ModelMixin): """ Behavior to create a `model` instance on POST requests """ @@ -616,7 +614,7 @@ class CreateModelMixin(QueryMixin): return Response(status.HTTP_201_CREATED, instance, headers) -class UpdateModelMixin(QueryMixin): +class UpdateModelMixin(ModelMixin): """ Behavior to update a `model` instance on PUT requests """ @@ -635,7 +633,7 @@ class UpdateModelMixin(QueryMixin): return self.model_instance -class DeleteModelMixin(QueryMixin): +class DeleteModelMixin(ModelMixin): """ Behavior to delete a `model` instance on DELETE requests """ @@ -651,7 +649,7 @@ class DeleteModelMixin(QueryMixin): return -class ListModelMixin(QueryMixin): +class ListModelMixin(ModelMixin): """ Behavior to list a set of `model` instances on GET requests """