I have a special operation on a model A, which adds objects (instances of model B) to a m2m relationship. I want to be able to both add instances of model B in the same request that I am creating an instance of model A. To do this I would use #list_route(methods=['post']).
But I also want to be able to add instances of model B to an instance of model A which has already been created. For this I would use #detail_route(methods=['patch']). However, the only difference in how these to views operate is in fetching/creating the instance of model A. Ideally I would make this a single function that tests request.method when fetching an instance of model A. That would look something like this:
#list_route(methods=['post'])
#detail_route(methods=['patch'])
def my_view(self, request, *args, **kwargs):
if request.method == 'POST':
# create and fetch instance of model A
instance = A.objects.create(some_paramaters_here)
else if request.method == 'PATCH':
instance = self.get_object()
# do things to add to instance
for thing in request:
instance.add(thing)
return 201 # and fetch json output from serializer that I omitted here
But I'm really concerned about using both decorators here. Will it work? Are best practices in this scenario any different?
Related
How can I use the current value of a field in update view to update another field?
I have a model employee. It has a field day_absent and amount_absent which shows the total deduction amount based on how many day_absent.
In update view the can set the day_absent.
When I can back to DetailView, say the day_absent is 1. but the deduction is still 0. I understand that this is because prior to save(), day_absent was still 0 and was changed to 1. So the question is how can i compute whatever is value entered in day_absent before it gets saved?
class PayrollTransactionUpdate(LoginRequiredMixin,UpdateView):
model = t_pay
template_name = 'payroll/transaction/update.html'
fields = ['day_absent']
def post(self,request,pk):
emp = t_pay.objects.get(pk=pk)
emp.amt_absent = emp.day_absent * emp.amt_rate
emp.save()
return super().post(request)
The UpdateView saves the object in the form_valid() method. Look at this invaluable site when you're using Django class-based views.
So you should override the form_valid() method, not the post() method:
def form_valid(self, form):
emp = form.save(commit=False)
emp.amt_absent = emp.day_absent * emp.amt_rate
emp.save()
self.object = amp
return super().form_valid(form)
Note that saving a field to Employee that can easily be calculated from two other fields is not recommended, as it could lead to inconsistent/corrupt data. Since amt_absent is just the multiplication of two other fields, why do you need to save it?
Alternatively, looking at what you're actually doing, this does not seem the responsibility of the view. If this is supposed to happen every time an Employee is saved, you could do it on the model itself:
class Employee(Model):
... # fields go here
def save(self, **kwargs):
self.amt_absent = self._get_amt_absent()
super().save(**kwargs)
# update other models here as well
In this situation, you don't need to override anything on the UpdateView.
I'm using the Django Rest Framework 3.2. I have a model "Report" with a ForeignKey to the model "Office".
I want to set the report.office based on the request.
This is how I did it (which I don't like, because it requires duplication):
In my view I differ between GET or POST/PUT/PATCH requests, where GET uses a nested Serializer representation, and POST/PUT/PATCH uses flat serializer representations.
Then I do in my view:
def perform_create(self, serializer):
serializer.save(office=Office.objects.get_only_one_from_request(self.request))
and in my FlatOfficeSerializer:
def validate(self, attrs):
if self.instance is not None:
instance = self.instance
instance_fields = instance._meta.local_fields
for f in instance_fields:
f = self.get_initial().get(f.name)
else:
instance = self.Meta.model(**attrs)
request = self.context.get('request')
instance.office = Office.objects.get_only_one_from_request(request)
instance.clean()
return attrs
It is clear that there is a duplication there and although it is working I don't like it. Without the duplication the validation fails (of course) on the instance.clean(), which calls the clean method of the model. I really need this, because my business logic is in the model's clean and save methods.
Django Restframework provides generic views for lists and single models from querysets. Which view class would be most appropriate for creating an endpoint that serves a statistical summary of a queryset?
I could surely build an APIView from scratch but I would like to reuse most of the settings on the ListAPIView (such as get_queryset, permission_classes, etc.) as well as url parameters.
The endpoints will be organized like this:
/api/data/ # data endpoint
/api/data/summary/ # summary endpoint
The summary endpoint will provide a single object that is not related to a single model instance.
Thank you for any best practice advice.
For now I came up with following solution:
# pseudo-code !
class DataView(ListAPIView):
"""Returns a queryset as a serialized and paginated list.
Set queryset, permissions, etc. here."""
def get_queryset(self):
# add complex lookup here
queryset = self.queryset
return queryset
class SummaryView(DataView):
"""Overwrite the get method to serve different
content, e.g. statistical summary."""
def summarize(self, request, *args, **kwargs):
"""This can be moved to a Mixin class."""
# make sure the filters of the parent class get applied
queryset = self.filter_queryset(self.get_queryset())
# do statistics here, e.g.
stats = {'count': queryset.count()}
# not using a serializer here since it is already a
# form of serialization
return Response(stats)
def get(self, request, *args, **kwargs):
return self.summarize(request, *args, **kwargs)
I'm trying to create a REST-ModelViewSet that has no model predefined, but takes a model when registered with the router. I need this to dynamically add models to my REST-API, without configuring any new viewsets or serializers.
My idea was to pass the model in the kwargs of __init__, but I can't figure out how to correctly do this. Here is what I tried:
//Viewset
class ThemeViewSet(viewsets.ModelViewSet):
def __init__(self, **kwargs):
self.model = kwargs['model']
self.serializer_class = None
super(ThemeViewSet, self).__init__(**kwargs)
def get_serializer_class(self):
if self.serializer_class is not None:
return self.serializer_class
class ThemeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = self.model
self.serializer_class = ThemeSerializer
return self.serializer_class
//Router:
router = routers.DefaultRouter()
router.register(r'mytheme', ThemeViewSet(model=mytheme), base_name='mytheme')
Now, if I try to print self.model in __init__, it correctly shows <class 'myapp.models.mytheme'> in the console, but Django still returns an error:
AttributeError at /api/mytheme/
This method is available only on the view class.
This error is raised by the classonlymethod-decorator. I don't really know what to make of this, is there any way to pass the model to __init__, or is there a different approach that I can try?
(I know that wq.db.rest has a router that does what I want, but I don't want to use wq. I haven't tried tastypie, would that make it easier/possible?)
Thanks in advance!
Django REST Framework expects that a ViewSet class is passed into the router, not a view instance. This is because the instance has to be created for each request, which prevents a lot of ugly issues with shared state and also follows the standard Django class-based views.
You may have better luck with having a method that creates a customized ViewSet class based on the model that is passed into it:
class ThemeViewSet(viewsets.ModelViewSet):
#classmethod
def create_custom(cls, **kwargs):
class CustomViewSet(cls):
model = kwargs["model"]
queryset = kwargs["model"].objects.all()
return CustomViewSet
Note that I'm also setting the queryset for the view, and DRF no longer accepts just a model since 2.4 was released.
This will create a new class each time it is called, and the model will automatically be set to the model that is passed into it. When registering it with the router, you would do something like:
router.register(r'mytheme', ThemeViewSet.create_custom(model=mytheme), base_name='mytheme')
This way you will still be passing a ViewSet class to the router, but it will be customized for the model that is passed in. You must make sure to set the base_name, or the router won't be able to generate the view names and you will eventually run into errors.
I have django model form MyModelFormA for the model ModelA (I am using these in FormView).
I want to pass initial values to the form using existing object of ModelA and create new object of it if changes occur.
I have been passing initial values like below:
def get_form_kwargs(self):
kwargs = super(MyFormView, self).get_form_kwargs()
kwargs.update({'instance': ModelAObject})
I'm not sure why but when the form is validated like below
def form_valid(self, form):
instance = form.save()
Its just updating existing instance object instead of creating new.
HTTP requests being stateless, how does the request knows an instance that is being passed processed in previous request
How to solve this?
I thought of doing something like
def get_initial(self):
initial = model_to_dict(MyModelAObject)
return initial
Actually There are only a subset of MyModelA fields in MyModelFormA. Passing all fields as dict initially, wouldn't create any trouble?
is there any much elegant way to handle it?
When you pass ModelForm an instance, it sets id field of that instance as initial of the form as well. So, if it receives an ID in the POST, its treats it as a existing object and updates it
You will need to pass individual field's initial value(i.e except id). The best way is to only pass the fields you need, in ModelForm, as initial.
def get_initial(self):
return {
'a': MyModelAObject.a,
....
}
Probably you can try this:
def form_valid(self, form):
if form.has_changed()
instance = form.save(commit=False)
instance.pk = None
#if you have id
instance.id = None
instance.save() #will give you new instance.
Check In Django 1.4, do Form.has_changed() and Form.changed_data, which are undocumented, work as expected? to see how form.has_changed() will work.