Django - Checking if objects exists and raising error if it does - python

I need to check if an object exists in a table. If the object doesn't exist I want to save it, if it does exist I want to refresh the page and tell the user that the identifier (quote number) already exists. I have this example code:
def some_method(request):
if request.method == 'POST':
form = SomeForm(request.POST)
if form.is_valid:
quote = request.POST.get('quote')
if SomeModel.objects.get(quote_number = quote).exists():
refresh with error #not sure how to do
else:
save object #I can do this part
The problem I am running into is that when I check if the object exists (and it does) then the code will raise an error saying the object doesn't exist before it hits the if. Which means on the webpage is full of coding information rather than the refresh with a message for the user.
I want to be able to have a little pop up message or something for the user to enter a new quote number rather than having the developer error page show up.
Am I going about this the correct way?

The problem is get returns an object, but exists only works with querysets. A Model.DoesNotExist error will be raised when you use get and the object does not exist.
You should use filter instead of get:
qs = SomeModel.objects.filter(quote_number = quote)
if qs.exists():
...
else:
...

I would do something like this:
from django.http import HttpResponseRedirect
from django.contrib import messages
...
try:
some_object = SomeModel.objects.get(quote_number=quote)
message.warning(request, 'some message')
return HttpResponseRedirect('some-url')
except SomeModel.DoesNotExist:
some_object = SomeModel.objects.create(quote_number=quote)
...
And in the template you can display the message just like so:
{% if messages %}
{% for message in messages %}
{{message}}
{% endfor %}
{% endif %}

You can use get_or_create. It returns a tuple with object retrieved (or created) and a boolean value. If boolean value is True when it's a new object, and if it's False when object already exists and you can throw desired error.

Related

Django template filter not raising exception when error occurs

I am building a custom template filter in Django to check if a user has permissions to view certain links in my navigation.
I am running into an issue where I want to know if the argument passed in is bad, which is an app_label, so I can throw an exception on that front and be notified.
Here is my HTML of the filter in use, that works without the or but with it, the conditional simply returns False without throwing the exception which is where I am confused.
Note: I have an app named 'reports', not 'repordts' so the exception should definitely be thrown. Also, you can simply test this on your end with any similar django template if...else clause with a filter that throws an exception
{% if request|has_perm_for_app_with_property:'repordts' or request|has_perm_for_app_with_property:'staff_reports' %}
This version throws the exception correctly:
{% if request|has_perm_for_app_with_property:'repordts' %}
Here is the filter code:
#register.filter()
def has_perm_for_app_with_property(request, app_label):
"""
"""
user = request.user
# ensure prop is set in session
if not request.session.get('property'):
return False
print(app_label)
# gather perms and show error if bad name
perms_for_app = Permission.objects.filter(content_type__app_label=app_label)
if not perms_for_app:
raise Exception(f'No Perms for App: {app_label}')
# check to ensure the permission passed is accurate
allowed = any([user.has_perm(perm) for perm in perms_for_app])
return allowed

TypeError & AssertionError when clicking on buttons in Django

Apologies for the long post, I am trying to implement a simple button which either add or remove an item from a watchlist.
While I initially managed to implement the "addwatchlist" function appropriately, I tinkered my code for a few hours and I somewhat am unable to wrap my head around it again.
Here is the error that I receive when pressing the "Add to Watchlist" button :
TypeError at /addwatchlist/10
Field 'id' expected a number but got <Listing: "Gloss + Repair Spray">.
Request Method: GET
Request URL: http://127.0.0.1:8000/addwatchlist/10
Django Version: 3.1.1
Exception Type: TypeError
Exception Value:
Field 'id' expected a number but got <Listing: "Gloss + Repair Spray">.
TRACEBACK :
watchlist.save()
▼ Local vars
Variable Value
id 10
request <WSGIRequest: GET '/addwatchlist/10'>
watchlist Error in formatting: TypeError: Field 'id' expected a number but got <Listing: "Gloss + Repair Spray">.
Here is the error that I receive when pressing the "Remove from Watchlist" button, note that this is the exact same error I initially received which in turn forced me to try and tweak the way "Add to Watchlist" function :
AssertionError at /removewatchlist/1
Watchlist object can't be deleted because its id attribute is set to None.
Request Method: GET
Request URL: http://127.0.0.1:8000/removewatchlist/1
Django Version: 3.1.1
Exception Type: AssertionError
Exception Value:
Watchlist object can't be deleted because its id attribute is set to None.
TRACEBACK :
watchlist.delete()
▼ Local vars
Variable Value
id 1
request <WSGIRequest: GET '/removewatchlist/1'>
watchlist <Watchlist: 1 Watchlist ID# None : admin watchlisted : "Iphone 11 Pro">
Models.py :
class Listing(models.Model):
owner = models.CharField(max_length=64)
title = models.CharField(max_length=64)
description = models.TextField(max_length=1024)
startingBid = models.IntegerField()
link = models.URLField(blank=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="category_id")
time = models.DateTimeField(auto_now_add=True)
active = models.BooleanField(null=False, default='True')
def __str__(self):
return f'"{self.title}"'
class Watchlist(models.Model):
user = models.CharField(max_length=64)
watchlist_listingid = models.ForeignKey(Listing, on_delete=models.CASCADE, related_name="watchlist_listingid", null=True)
def __str__(self):
return f'{self.watchlist_listingid_id} Watchlist ID# {self.id} : {self.user} watchlisted : {self.watchlist_listingid}'
Views.py :
#login_required
def watchlist(request):
if request.user.username:
return render(request, "auctions/watchlist.html", {
"items" : Watchlist.objects.filter(user=request.user.username),
"watchlist_listingid" : Watchlist.objects.values_list('watchlist_listingid', flat=True).filter(user=request.user.username),
"watchlist_count" : len(Watchlist.objects.filter(user=request.user.username))
})
def addwatchlist(request, id):
if request.user.username:
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=Listing.objects.filter(id=id).first())
watchlist.save()
return redirect('listing', id=id)
else:
return render('auctions/watchlist.html', {})
def removewatchlist(request, id):
if request.user.username:
# all_entries = Watchlist.objects.values_list('watchlist_listingid', flat=True).filter(user='admin')
# for i in all_entries:
# if i == id:
# removeMe = i
# else:
# removeMe = 'TEST ERROR'
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=id)
watchlist.delete()
return redirect('listing', id=id)
Urls.py :
path("listing/<int:id>", views.listing, name="listing"),
path("watchlist", views.watchlist, name="watchlist"),
path("addwatchlist/<int:id>", views.addwatchlist, name="addwatchlist"),
path("removewatchlist/<int:id>", views.removewatchlist, name="removewatchlist")
The Add/Remove buttons are hosted on listing.html :
{% if user.is_authenticated %}
<p>
{% if watchlisted %}
<button class="btn btn-danger">Remove from watchlist</button>
{% else %}
<button class="btn btn-success">Add to watchlist</button>
{% endif %}
</p>
{% endif %}
Questions :
Am I complicating myself by using Watchlist.watchlist_listingid as a
foreign key for Listing? I have seen quite a few other posts where
people would tend not to use foreign keys at all, though I believe it
might be not optimized.
I could eventually be using a form to envelop those buttons but I
already have a form in my listing.html page (to add comments).
Tackling the data from a form might be easier though I have not
managed to find out how to implement several distinct Django forms on
one HTML page. What do you consider best practice? Forms or direct
link summoning functions?
For "Add to Watchlist", why do I obtain def __str__(self):return f'"{self.title}"'instead of simply adding a new entry in my
Watchlist table?
Finally, for "Remove from Watchlist",why do i receive "id attribute is set to None" when I know for a fact that on my example,the
Listing.id for "Iphone 11 Pro" is 1, and technically speaking,
Watchlist.id (which does not show at all in my model) is 11 based on
the Django Admin page. Even hardcoding 11 to force its deletion off
the Watchlist table still returns this "none" error.
Looking forward to your replies!
Per your specific error, the problem is in this line, which should be clear from the error message:
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=Listing.objects.filter(id=id).first())
id is expecting an int (the id of a Model instance), but you are actually passing it an object instance of your Listing model; filter() returns a queryset, and then .first() returns the first object in that queryset.
That's why the error is telling you that "field id", which expects an int, is instead getting "<Listing: "Gloss + Repair Spray">" -- that's an actual instance of your Listing model.
Solution:
Simply adding .id after the .first() should fix your problem, so you are actually passing the id of that object to the id field:
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=Listing.objects.filter(id=id).first().id)
That should resolve your specific issue; having said that, though, I highly recommend you use forms for these buttons. Currently, you are allowing modification of the contents of your database in an insecure fashion. Any activity that modifies database content should be delivered as a POST request. You can do this with forms quite easily, even if you aren't using any actual input-fields. The button will submit the empty form as a POST request, you can include a csrf token in your form tag in the template, and the db will be modified accordingly. Another option, if you really don't want to use forms, is to use AJAX calls (via jQuery), which can be sent as POST requests without the use of forms. Explaining how to do that is beyond the scope of this response, but there is plenty of info here on SO.
Lastly, if a Watchlist is tied to a User, you should probably consider a different database schema entirely for getting results-- that is, one that actually ties the User model to the item they are adding to their watchlist (it's possible, though, that I'm misunderstanding your intent here, and maybe you don't want that). You could connect the User model and the Listing models via a Many-to-Many relationship, since a User can 'watchlist' many listings, and a listing can be 'watchlisted' by many users.
Fix for : "Field 'id' expected a number but got <Listing: "Gloss + Repair Spray">."
Simply add .id after .first()
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=Listing.objects.filter(id=id).first() = <Listing: "Gloss + Repair Spray">
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=Listing.objects.filter(id=id).first().id) = 6 (which is the ID in my database)
Fix for : Watchlist object can't be deleted because its id attribute is set to None.
watchlist = Watchlist.objects.get(user=request.user.username, watchlist_listingid=id)
GET the whole row in your table and assign it to a variable before using delete()
Instead of the faulty :
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=id)
Finally, I found out that to use different Django Forms in the same HTML page, you can set the action of the form to a path url in URLS.PY which would then trigger a special functions in Views.py.

Django forms with clean_field(self) return empty element on raise

I created a ModelForm in this way.
class Mant(forms.Modelform):
start_date=forms.CharField()
stop_date=forms.CharField()
def clean_start_date(self):
tz=self.request.session.get('timezone','UTC')
data=self.cleaned_data['start_date']
try:
date_parsed=arrow.get(data,'YYYY-MM-DD HH:mm').replace(tzinfo=tz)
except ParserError as e:
raise forms.ValidationError("invalid format")
return date_parsed.format('YYYY-MM-DD HH:mm:ssZZ')
def clean_stop_date(self):
tz=self.request.session.get('timezone','UTC')
data=self.cleaned_data['stop_date']
try:
date_parsed=arrow.get(data,'YYYY-MM-DD HH:mm').replace(tzinfo=tz)
except ParserError as e:
raise forms.ValidationError("invalid format")
return date_parsed.format('YYYY-MM-DD HH:mm:ssZZ')
The form works nice. validate current dates and all. but my problem is that when there is some problems parsing the date (or any other thing that i add that raise an error) the field comes empty and the user need to select the date all over again.
Every time I see a validation error normal in django the field filled by the user comes back so the user can correct it. but in my case when clean_field is replaced the value filled by the user disappears.
there is a way to Fix this?.
Also looks like if the firs element Raise an exception the validation is stopped altogether and all the others errors aren't returned and theirs fields emptied :(
Sorry for answering my own question.
My problem was at template level.
my template was waging for a diatomite object because need to localize the date in the timezone of the user. but when there was an error in the form. the value did not contain a datetime object. just a buffer with the previous input. so the timezone date tags failed to parse the data. printing an empty value.
now the part in the form looks like this.
<input class="form-control datetimepicker" data-date-format="YYYY-MM-DD HH:mm" id="id_start_date" name="start_date" type="text"
{%if form.errors %}
value="{{field.value}}"
{%else%}
value="{{field.value|timezone:user_timezone|date:"Y-m-d H:i"}}"
{% endif %}
when the form came with a errors array Im aware of this and just print the value

Django database inquiries. Finding duplicates in the database and stopping them from being submitted

I'm trying to go inside of my database and see if a certain submission has been submitted before and if it has stop it from being submitted again. Currently I have this bit of code testing each field of the form (obviously changes per field but i thought I'd only show one field for simplicity)
if request.method == 'POST':
Cashtexts.objects.filter(cashTexts=request.POST['cashTexts']).exists() == False:
Then if it does not exist it goes on and save the submissions into the database. If it returns true it goes to an else statement that tells the user that what they entered has already been entered. previously this type of thing worked but I changed some variable names and now it stopped working. I've been browsing the code over a few time so I thought maybe something was fundamentally wrong with this type of filter but it makes sense in my head.
def submit_win(request):
if request.method == 'POST':
if Cashtexts.objects.filter(cashTexts=request.POST['cashTexts']).exists() or Cashtexts.objects.filter(superPoints=request.POST['superPoints']).exists() or Cashtexts.objects.filter(varolo= request.POST['varolo']).exists() or Cashtexts.objects.filter(swagbucks = request.POST['swagbucks']).exists() or Cashtexts.objects.filter(neobux = request.POST['neobux']).exists() or Cashtexts.objects.filter(topline=request.POST['topline']).exists() or Cashtexts.objects.filter(Paidviewpoint=request.POST['Paidviewpoint']).exists() or Cashtexts.objects.filter(cashcrate=request.POST['cashcrate']).exists() == False:
form = CashtextsForm(request.POST)
if form.is_valid():
form.save()
ref_create = Cashtexts.objects.filter(cashTexts=request.POST['cashTexts'])
return render_to_response('submitted_page.html', {'ref_create': ref_create})
else: #Error message reading wither you didnt insert codes or you enter the same code twice
error = 'That code has already been submitted'
ref_create = CashtextsForm()
return render_to_response('submit.html', {'ref_create': ref_create,'error':error}, context_instance=RequestContext(request))
else: #displays the page when the user asks to go to the submit page
ref_create = CashtextsForm()
return render_to_response('submit.html', {'ref_create': ref_create}, context_instance=RequestContext(request))
Also, I turned Cashtexts.objects.filter(cashTexts=request.POST['cashTexts']) intoa variable and passed it to my template to see what it was returning and it was returning objects that should have told the statement True. but it just seemed to ignore them and submit them anyway.
I suppose i could delete all the previous objects that match the thing they entered upon submission but that makes it less secure and could lead to users submitting over and over again thinking that they are getting it more in there. I'd rather just stop it before it happens.
You can just set the definition of your cashTexts field to unique in the models definition:
def Cashtexts(models.Model):
name = CharField(max_length=50, unique = True)
Just set the "unique" argument to true for each field you want to be unique and you're done. The way Django's forms API is working, the form fields will take over all the necessary error handling.
With the model fields set to unique where necessary you can have this much easier:
def view(request):
if request.method == 'POST':
form = CashtextsForm(request.POST)
""" the next line is going to check uniqueness for each
of the models fields where you have set unique = True
If the field is not unique, it s field will have an
attribute error, which you can then render to the template """
if form.is_valid():
form.save()
else:
form = CashtextsForm()
context = RequestContext(request)
# now i pass the form to the template as 'ref_create'
context.update( {'ref_create':form} )
return render_to_response('submit.html',context)
Then you simply render that form in your template (either with form.as_p(), form.as_table() or with some custom code like this):
{% for field in ref_create %}
{{ field.label_tag }}
{{ field }}
{{ field.errors }}
{% endfor %}
Each field, set to unique in the model definition, which which would not be unique in the database any more if your form is saved, will now have an error message saying something like "A Cashtext with this name allready exists".
Customizing you error messages can be done in two ways: 1: customize the forms (i do not recommend that, until you have better understandings of how Django works) 2: instead of {{ field.errors }} write something like this:
{% if field.errors %}
The Cashtext field {{field.name}} value should be unique.
{% endif %}

Django: Want to display an empty field as blank rather displaying None

I have a template called client_details.html that displays user, note and datetime. Now sometimes, a client may not have an entry for user, note and datetime. What my program will do instead is display None if these fields are empty. I do not want the to display None. If a field has no value I don't want to see any value e.g. let it be blank if possible instead of displaying None.
views.py
#login_required
def get_client(request, client_id = 0):
client = None
try:
client = models.Client.objects.get(pk = client_id)
except:
pass
return render_to_response('client_details.html', {'client':client}, context_instance = RequestContext(request))
template
{{client.datetime}}<br/>
{{client.datetime.time}}<br/>
{{client.user}}<br/>
{{client.note}}<br/>
Use the built-in default_if_none filter.
{{ client.user|default_if_none:" " }}
{{ client.user|default_if_none:"" }}
you may use:
{% if client %} {{client.user}} {% else %} {% endif %}
Checking with an if is enough, so you may not user else block if you want...
this is such a strange problem.
I have a good idea for it. If you want to modify your field at display time than rather checking it at template , check it at your model class.
ExampleModel(models.Model):
myfield = models.CharField(blank=True, null = True)
#property
def get_myfield(self)
if self.myfield:
return self.myfield
else:
return ""
Use it in your template directly instead of field.
{{ExampleModel.get_myfield}}
you never need to change your template to change this field in future, just modify you property.

Categories