I am fairly new to CBV and I have the following question:
I see that there is CreateView that would show you the "empty" form to create a new database entry and there is UpdateView that would show you the form for the existing entry for update.
What I need is some kind of mix of it: present the user with the form for the lastly viewes/updated entry, but if the database has no entries yet (e.g. new user), present the user with a default ("empty") form.
So, there are 2 points here:
Have a model that contains lastly viewed/updated entry per user: what should this model be?
Have a view that allows to present forms as specified above. Is there a generic or semi-generic way to do that in Django? What kind of CBV should I be using?
Thanks.
I haven't tested this so I'm not sure if this will work.
from django.views.generic.edit import ModelFormMixin, ProcessFormMixin
class MyView(ModelFormMixin, ProcessFormMixin):
def get(self, request, *args, **kwargs):
try:
self.object = MyModel.objects.latest("id")
except MyModel.DoesNotExist:
self.object = None
return super(MyView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
try:
self.object = MyModel.objects.latest("id")
except MyModel.DoesNotExist:
self.object = None
return super(MyView, self).post(request, *args, **kwargs)
Related
I have the following view:
class ReadClass(generics.RetrieveUpdateDestroyAPIView):
queryset = MyCModel.objects.all()
serializer_class = MySerializer
def post(self, request, *args, **kwargs):
''' defined my post here'''
I know retrieveupdatedestroyapiview doesn't have post in it. And I have created my own post in the view here and on the front end, I see both post and put! Is there any way to remove the put.
Or is there any other way to do it better, I tried using ListCreateApi view. The problem with that is while it gives me the post functionality, it lists all the values, while I am looking for a specific pk. I cannot see any other generic view that gives me get and post functionality.
EDIT
I have added the edit as requested, try and except might seem unnecessary here at the moment, but I will add more functionality later on.
class ReadClass(generics.GenericAPIView, mixins.CreateModelMixin, mixins.RetrieveModelMixin):
queryset = MyCModel.objects.all()
serializer_class = MySerializer
def post(self, request, *args, **kwargs):
try:
s1 = MySerializer.objects.get(mRID=kwargs["pk"])
serializer = MySerializer(s1, data=request.data)
except MySerializer.DoesNotExist:
pass
if serializer.is_valid():
if flag == 0:
pass
else:
serializer.update(s1,validated_data=request.data)
else:
return Response(serializer.errors)
urlpatterns = [path('temp/<int:pk>', ReadClass.as_view(), name = " reading"),]
DRF has mixins for List, Create, Retrieve, Update and Delete functionality. Generic views just combine these mixins. You can choose any subset of these mixins for your specific needs. In your case, you can write your view like this, if you only want Create and Retrieve functionalty:
class ReadClass(GenericAPIView, CreateModelMixin, RetrieveModelMixin):
queryset = MyCModel.objects.all()
serializer_class = MySerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
This would provide default functionality for post and get requests. If you prefer, you can override post method like you did in your example to customize post requset behavior.
You can read more about mixins and generic views here
I am trying to specify a specific method of handling file uploads for a class based view. Per the docs this can be achieved by something like:
from django.core.files.uploadhandler import TemporaryFileUploadHandler
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
If i specify this in post method of a FormView like so:
def post(self, request, *args, **kwargs):
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
return super().post(self, request, *args, **kwargs)
I get:
AttributeError: You cannot set the upload handlers after the upload has been processed.
Variants like yield the same result:
def post(self, request, *args, **kwargs):
self.request.upload_handlers = [TemporaryFileUploadHandler(request=self.request)]
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
However when i do this in the get method this is ineffective:
def get(self, request, *args, **kwargs):
request.upload_handlers = [TemporaryFileUploadHandler(request=self.request)]
return super().get(self, request, *args, **kwargs)
If I upload a small file it still uses the default django.core.files.uploadhandler.MemoryFileUploadHandler.
What am I doing wrong?
EDIT
Also when i try to mirror what is suggested in the note, I get the same AttributeError:
from django.views.decorators.csrf import csrf_exempt, csrf_protect
#csrf_exempt
def post(self, request, *args, **kwargs):
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
return self._post(request, *args, **kwargs)
#csrf_protect
def _post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
Ok, finally got it to work (using the suggestions provided by #Alasdair). Setting a method decorator(crsf_exempt) on post is not engough it needs to be on dispatch. For anyone struggling with this in the future, it goes like this:
from django.views.generic import FormView
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt, csrf_protect
#method_decorator(csrf_exempt, 'dispatch')
class UploadDataSetView(FormView):
def post(self, request, *args, **kwargs):
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
return self._post(request)
#method_decorator(csrf_protect)
def _post(self, request):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
Also it will fail if you remove the {% csrf_token %} from your template (which is what you want).
Because you cannot change upload handler in view as it is something that gets invoked prior to your view function.
Get shouldn't collect post parameters so it behaves accordingly.
Upload Handlers
When a user uploads a file, Django passes off the file data to an
upload handler – a small class that handles file data as it gets
uploaded. Upload handlers are initially defined in the
FILE_UPLOAD_HANDLERS setting, which defaults to:
["django.core.files.uploadhandler.MemoryFileUploadHandler",
"django.core.files.uploadhandler.TemporaryFileUploadHandler"]
If you want different upload handler you can change FILE_UPLOAD_HANDLERS settings
If this is not enough you can write your custom upload handler
Edit:
Also, request.POST is accessed byCsrfViewMiddleware which is enabled
by default. This means you will need to use csrf_exempt() on your view
to allow you to change the upload handlers.
DetailStory subclasses DetailView and ModelFormMixin thus presenting the DetailView of a certain story and a form at the end. However, on filling the form and submitting the data, the data is saved in the databases but it is still shown on the form (in addition to the one now displayed on the DetailView). How do I present an empty form after submitting it? (Here is the code sample)
class DetailStory(DetailView, ModelFormMixin):
model = Story
template_name = 'stories/detail_story.html'
context_object_name = 'detail'
form_class = CommentForm
def get(self, request, *args, **kwargs):
self.object = None
self.form = self.get_form(self.form_class)
return DetailView.get(self, request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = None
self.form = self.get_form(self.form_class)
if self.form.is_valid():
obj = self.form.save(commit=False)
obj.user = self.request.user
obj.memoir = self.get_object()
self.object = obj.save()
return self.get(request, *args, **kwargs)
def get_object(self):
item_id = crypt.decode(self.kwargs['story_id'])[0]
obj = get_object_or_404(Story, Q(privacy='public') | Q(user_id=self.request.user.id), pk=item_id)
return obj
get_form uses the request data to construct the form as per the docs
If the request is a POST or PUT, the request data (request.POST and request.FILES) will also be provided.
So simply don't make your post function go back through the get, just have it redirect to your required place or do anything differently to pointing it at the get function.
return redirect('mynamespace:story_detail', story_id=self.object.pk)
You may wish to read this answer for a list of technical details you should consider whilst making your application. In particular,
Redirect after a POST if that POST was successful, to prevent a refresh from submitting again.
First the idea: I have a manger, who manages different branches each of which has restaurants assigned to them. I want him to be able to make orders and choose restaurants available for his specific branch. I use a class with static values to remember which manager is making the order.
Second: the code.
forms.py:
class NewOrderFormManager(Form):
restaurant = forms.ModelChoiceField(queryset=None)
# other fields here
def __init__(self, *args, **kwargs):
super(NewOrderFormManager, self).__init__(*args, **kwargs)
branches = StaticData.get_branches_assigned_to_manager()
self.fields['restaurant'].queryset = Restaurant.objects.filter(branch__in=branches)
views.py:
class NewOrderFromManagerFormView(FormView):
template_name = 'admin_custom/new_order_from_manager_form.html'
form_class = NewOrderFormManager
def get(self, request, *args, **kwargs):
StaticData.find_branches_corresponding_to_manager(request.user)
return super(NewOrderFromManagerFormView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
StaticData.find_branches_corresponding_to_manager(request.user)
return super(NewOrderFromManagerFormView, self).post(request, *args, **kwargs)
def form_valid(self, form):
# some code to be executed
storage class:
class StaticData:
branches = None
#classmethod
def find_branches_corresponding_to_manager(cls, user):
cls.branches = None
if roles.has_role(user, roles.ROLE_ADMIN):
cls.branches = Branch.objects.all()
elif roles.has_role(user, roles.ROLE_COURIER_MANAGER):
cls.branches = user.couriermanager.branches.all()
#classmethod
def get_branches_assigned_to_manager(cls):
return cls.branches
Third: the problem.
There seams to be some trouble with posting a request for some specific restaurant. When I access clean() method I can find an error saying that the choice I've made is not valid and I should choose another. I am using superadmin'account for this purpose and I've checked that I get correct branches when I callStaticData. Therestaurantsimply doesn't appear incleaned_data`. I've tried both digging into django's documentation and sourcecode but to na avail as for now.
UPDATE:
I've inspected the server log output and I see that on clicking "submit" button it uses GET:
"GET /custom_admin/new_order_from_manager/restaurant=5&price=0.04&phone_number=123123123&street=Gwarna&house_number=1&flat_number=&comment= HTTP/1.1" 200 13887
It seems that the lack of csfr token was a problem, so I've added it to my form:
<form method="post">
{% csrf_token %}
I am performing some more validation fater the main form has been validated.
I am using class based views
def form_valid(self, request, *args, **kwargs):
some custom logic
if(false)
Here i want to return to form invalid page with my custom message
I am processing the file in form_valid and thats got many steps which i need to check for more validation. so i don't want to move all that logic out to another function
You shouldn't be doing validation in form_valid. Do it in the form, either in Form.clean or Form.clean_FOO. Refer to the Django documentation on form validation.
If you insist on mutilating your code-base, then try the following:
def form_valid(self, request, *args, **kwargs):
is_actually_valid = some_custom_logic()
if not is_actually_valid:
return self.form_invalid(request, *args, **kwargs)