Django Overwrite form data saved - django

I've posted about this problem before, but I still haven't found a solution so I'm hoping I'll have better luck this time.
I have a form that takes inputted data by the user. In another page, I am creating the identical form that the user has populated (pre-filled with that information) for editing purposes. Users will come to this page to EDIT the information they have already put in. My problem is that it isn't overwriting the instance.
def edit(request):
a = request.session.get('a', None)
if a is None:
raise Http404('a was not found')
if request.method == 'POST':
form = Name_Form(request.POST, instance=a)
if form.is_valid():
j = form.save( commit=False )
j.save()
else:
form = Name_Form( instance = a )
For this form, I'm using "unique_together" for some of the values. I'm also calling on `{{ form.non_field_errors }} in the template.
What is happening is when I make changes in the editing view, if the fields changes involves those defined in "unique_together" then an error is returned telling me that the instance already exists. Otherwise it saves a new instance. It isn't OVERWRITING.
Note that the reason i am using unique_together is that I want to prevent users from initially inputting the same form twice (before the editing stage, in the initial inputting view).
Any ideas?
EDIT: note that "a" refers to a session that includes a drop down box of all the available instances. This carried forward will indicate which instance the user wants to edit.
`

Why not do a database lookup of the model your trying to save and pull the fields from the form to the model then save the model?

Instead to store model a in session you should store it on database. Then edit it:
def edit(request, pk):
a = A.objects.get( pk = pk)
...
pk it the a identifier, you can send it to view via urls.py. I encourage to you to use POST/Redirect/GET pattern.
You can add a 'state' field on your model to control workflow (draft, valid)

You should not save objects in the session. If you really need to use a session - save a PK there and retrieve object right before giving it to Form. But the better solution is to send it in GET or POST parameters or included in url. Sessions are unreliable, data inside it can be destroyed between user's requests.
And you can retrieve value from a session in a more pythonic way:
try:
a = request.session['a']
except KeyError:
raise Http404('a was not found')

Related

How do I get a Django redirect to work properly when updating my database?

I can't get my redirect to work correctly after updating a bicycle in my Django app.
When I use my bikes_update() method and update the bicycle profile, if my redirect is just to '/bikes', I'm taken back to the bicycle collection/bicycles owned with no issue. But when I try and redirect back to the profile of the bike I just editted, I get the error that my return pathway doesn't match any of the url patterns. Even though that's the same pathway that initially takes me to the bicycle profile view (not the same one where I edit the bicycle, that is a different view).
I've pasted the bikes_update() method here:
def bikes_update(request, bike_id):
if request.method != 'POST' or 'user_id' not in request.session:
return redirect("/")
this_bike = Bike.objects.filter(id=bike_id).update(
name = request.POST['bike_name'],
model = request.POST['model_id'],
)
return redirect('/bikes')
return redirect('/bikes/<int:bike_id>')
return redirect('/bikes/<int:bike_id>/')
I obviously only use one of the redirects, not all the three in the final block. But I can't figure out why at least one of the final two do not work. When I have the bottom two redirects commented out, the 'return redirect('/bikes')' line works fine, but it takes me to the page listing all of my bikes when I want to go back the profile of the bike I just editted so I don't have to navigate back to it from the bicycle collection.
Navigating to /bikes/int:bike_id/ works fine when going to the profile initially from the list of bikes, but after I finishing editing I run into this error even though it's the same line as what got me to the profile in the first place. As soon as I click the submit button and try to redirect back to /bikes/int:bike_id/ I get my error.
I'm able to update the bicycle profile correctly, so it's something to do with this redirect, but I don't get it.
I don't understand how line 12 in my screen snippet and the current path (both highlighted in the image), aren't the same/don't match.
If you want to redirect on /bikes/<int:bike_id>/ then you can use a format string like this...
def bikes_update(request, bike_id):
if request.method != 'POST' or 'user_id' not in request.session:
return redirect("/")
this_bike = Bike.objects.filter(id=bike_id).update(
name = request.POST['bike_name'],
model = request.POST['model_id'],
)
return redirect(f'/bikes/{bike_id}/')
You have to add the id here not this "int:bike_id". put your id variable here like this redirect(f'/bikes/{Id}').
redirect can take a model as an argument, so you could do something like:
def bikes_update(request, bike_id):
if request.method != 'POST' or 'user_id' not in request.session:
return redirect("/")
this_bike = Bike.objects.filter(id=bike_id).update(
name = request.POST['bike_name'],
model = request.POST['model_id'],
)
this_bike = get_object_or_404(Bike, pk=bike_id)
return redirect(this_bike)
You will need a get_absolute_url method in your models.py though. For example:
def get_absolute_url(self):
return reverse("bikes", kwargs={"pk": self.pk})
This can be used for other things in Django as well (including usage in templates).
More info here: Django Docs
Of course, as you're obtaining the object now for this purpose, you may want to consider refactoring (not using a queryset update and perhaps changing the this_bike attributes instead) how you update it in the first place, as you're only dealing with a single object here.

Rejecting form post after is_valid

I'm attempting to overwrite the RegistrationView that is part of django registration redux.
https://github.com/macropin/django-registration/blob/master/registration/backends/default/views.py
Essentially, I want to implement logic that prevents the registration from happening if some condition is present. Checking for this condition requires access to the request.
I'm thinking that I might be able to subclass the register function within this Class based view.
def register(self, form):
#START OF MY CODE
result = test_for_condition(self.request)
if result == False:
messages.error(self.request, "Registration cannot be completed.", extra_tags='errortag')
return redirect('/access/register')
#END OF MY CODE
site = get_current_site(self.request)
if hasattr(form, 'save'):
new_user_instance = form.save(commit=False)
else:
new_user_instance = (UserModel().objects
.create_user(**form.cleaned_data))
new_user = self.registration_profile.objects.create_inactive_user(
new_user=new_user_instance,
site=site,
send_email=self.SEND_ACTIVATION_EMAIL,
request=self.request,
)
signals.user_registered.send(sender=self.__class__,
user=new_user,
request=self.request)
return new_user
What is the correct way to do this using class based views? I've worked almost exclusively with function based views so far.
Thanks!
As per my knowledge, it is recommended to subclass "AsbtractBaseUser" and "BaseUserManager" for user management in Django.
In CustomUserModel, you can define required fields added to the existing fields from BaseUserManager.
In CustomUserManager, you can override create_user, create_staff_user, create_superuser, and save_user. You can define a few conditions that should be checked before registering the user and also you can add permissions or groups to the user based on the role in the "save_user" method.
Create CustomRegistrationSerializer, that controls the fields that need to be displayed on the registration page, and validation of those fields can also be defined.
In POST method of CBV, you can check condition that requires "request" before serializing data. If condition is met, you can continue with serializing and saving otherwise, you can return appropriate response.
Django : https://docs.djangoproject.com/en/3.2/topics/auth/customizing/#extending-the-existing-user-model

Sending form to another view django

I am building a website and I want various views that will ask the user to request a quote from our page. I want to keep the code as DRY as possible so I am writing a view quote which will receive the quote requests from various views and, if there is a validation error redirect back to the page that made the request. I managed to solve this using the super bad practice 'global variables'. I need a better solution, I would like redirecting to respective view with the current form so I can iterate through the form.errors. Here is my code:
def send_quote(request):
form = Quote(request.POST)
if form.is_valid():
# do stuff when valid
return redirect('Support:thanks', name=name or None)
quote_for = request.POST['for_what']
global session_form
session_form = form
return redirect('Main:' + quote_for) # Here I would like to send form instead of storing in global variable`
You can use the HttpResponseRedirect function, and pass as argument the page that made the request.
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
All the META data is store on a dictionary, if you want to learn more check the documentation.
https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.META
If you redirect to the referrer, form.errors will be empty, as redirection is always a GET request.
I can think of two solutions to your problem:
Submit forms asynchronously using JavaScript and so populate the errors
Make all the views containing the form support POST - one way to do this would be to create a base class that inherits from FormView
The second option is a typical way of handling forms in Django - you process both POST and GET inside the same view.
After two days of searching I finally found the answer. Instead of saving form in request.session I just save request.POST and then redirect. Here is the code:
def send_quote(request):
form = Quote(request.POST)
if form.is_valid():
# do stuff when valid
return redirect('Support:thanks', name=name or None)
quote_for = request.POST['for_what']
request.session['invalid_form'] = request.POST
return redirect('Main:endview')
def endview(request):
session_form = request.session.pop('invalid_form', False)
if session_form:
form = Quote(session_form)
# render template again with invalid form ;)
Now I can repeat this with all the views I want and just change the what_for input of each form to match the respective view (Like I intended).

Prompting registration after creating content but before save

I am trying to implement a page using Django where the user is able to create content, hit submit, and then is prompted to register or login if they are not currently logged-in. One of the fields in the model for the content references the user, so they must be logged-in to create it, however I want them to be able to input all of the data before they are prompted with logging-in. I have used the #loginrequired decorator on the view, but that forced the user to be logged-in before they create the content. Here is the view currently:
def search(request):
form = LocationInput()
if request.method == "POST":
form = LocationInput(request.POST)
if form.is_valid():
t = Trip(trip_name = form.cleaned_data['city'])
t.user = request.user
t.save()
form.save()
l = Location.objects.get(google_id=form.cleaned_data['google_id'])
l.trips.add(t)
l.save()
return HttpResponseRedirect('/trips/')
return render(request, 'frontpage/search.html', {'form': form})
It loads the form, creates an object that needs a user associated with it so I need to register the user but keep the data from LocationInput(request.POST) until after the user has registered. Any help would be greatly appreciated!
I can see two options:
Allow NULL for user reference, save the content in a state "pending user login or sign up", and store the ID of the content object in a session. Vacuum "old" contents with no user reference on a regular basis.
Save the whole data of the form in a session.
I like the first one better since you, as a superuser, have access to the content even if the user didn't login or signup, and if the user contact you later telling you he had troubles signing in, you will be able to help and recover the content he submitted. While if everything is stored in session, once the session is deleted, it's all lost.

Image not displayed after Django form save

I have a formset (more specifically a generic inline formset) whose forms have an ImageField. After working around what seems to be a few Django bugs, everything is working fine, except that when I fill the blank form with a new image and click save, the page refreshes after the POST request and the new image is not there. The other fields of the form I just saved are there and are properly filled, but the image URL doesn't show up. The database record is saved correctly and if I simply refresh the page, the image shows up correctly. But I can't figure out how to return from the POST with all the newly saved information without having to refresh the page an extra time.
In case it's relevant, for this ImageField I am using a custom Storage class which handles saving the image in a remote server through an API.
A workaround that solves the problem but which in my opinion shouldn't be necessary:
class ProductImagesView(View):
...
def post(self, request, id):
product = get_object_or_404(Product.objects.by_id(id))
image_formset = ProductImageInlineFormset(
request.POST, request.FILES, instance=product)
if image_formset.is_valid():
image_formset.save()
image_formset = ProductImageInlineFormset(instance=product) # Workaround
return render(...)
You can find more details about my code in this other question:
Django BaseGenericInlineFormSet forms not inheriting FormSet instance as form instance related_object
Any idea why this is happening? Am I doing something wrong or is this a Django bug? Thanks.
UPDATE
One thing I forgot to say: the formset that shows up after saving not only has a blank Image field for the newly created Image object, it also doesn't have the extra blank form for new records that should be there. It also only appears after a refresh.
(I pass extra=1 to the generic_inlineformset_factory):
ProductImageInlineFormset = generic_inlineformset_factory(
Image, form=ProductImageForm, extra=1)