Contextual form validation in Django - django

I want to do "contextal" form validation in django. Consider this case:
PLACE_TYPES = (
('RESTAURANT', 'Restaurant'),
('BARCLUB', 'Bar / Club'),
('SHOPPING', 'Shopping'),
)
RESTAURANT_FORMAT_CHOICES = (
('FAST_FOOD', 'Fast Food'),
('FAST_CASUAL', 'Fast Casual'),
('CASUAL', 'Casual'),
('CHEF_DRIVEN', 'Chef Driven'),
)
class Place(models.Model):
place_type = models.CharField(max_length=48, choices=PLACE_TYPES, blank=False, null=False)
name = models.CharField(max_length=256)
website_1 = models.URLField(max_length=512, blank=True)
hours = models.CharField(max_length=1024, blank=True)
geometry = models.PointField(srid=4326, blank=True, null=True)
#Restaurant Specific
restaurant_format = models.CharField(max_length=128, choices=RESTAURANT_FORMAT_CHOICES, blank=True, null=True)
So in the django admin, the corresponding form for Place will have pulldown menu with choices like "restaurant, bar, club", and there is another field called "restaurant_format".
Validation should make sure restaurant_field cannot be null if the first pulldown was set as "restaurant".
I am trying something like this:
class PlaceAdminForm(forms.ModelForm):
def clean(self):
if self.cleaned_data['place_type'] == 'RESTAURANT':
if self.cleaned_data['place_type'] is None:
raise forms.ValidationError('For a restaurant you must choose a restaurant format')
but get this error:
Exception Type: KeyError
Exception Value:
place_type
Exception Location: /place/admin.py in clean, line 27

i think i got it working with this clean routine:
def clean(self):
cleaned_data = self.cleaned_data
place_type = cleaned_data.get("place_type")
restaurant_format = cleaned_data.get("restaurant_format")
if place_type == 'RESTAURANT':
if self.cleaned_data['restaurant_format'] is None:
raise forms.ValidationError('For a restaurant you must choose a restaurant format')
# Always return the full collection of cleaned data.
return cleaned_data

Related

Django - Allow only one filed to fill in the form

I am using below django model for forms
class Post(models.Model):
author = models.ForeignKey(CustomUser,on_delete=models.CASCADE,)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
post_url = models.URLField(max_length = 200, blank = True)
picture = models.ImageField(upload_to='Image_folder', height_field=None, width_field=None, max_length=100, blank = True)
slug = models.SlugField(unique=True, blank=True)
I want to allow user to enter only one field either post_url or picture but not both. Can some give some an idea how to implement this?
# you can use clean or clean_yourfieldname function in your forms.py class
For Example
def clean(self):
cleaned_data = super().clean()
post_url = cleaned_data.get("post_url")
picture = cleaned_data.get("picture")
if post_url and picture:
raise forms.ValidationError(
"Please fill either post_url or picture field, but not both"
)
return cleaned_data

Django AttributeError and query does not exist error

File "C:\Users\NICSI\Desktop\lastnow\mynew\cheque\models.py", line 39, in st
r
return (self.related.relation.username).title()
AttributeError: 'NoneType' object has no attribute 'relation'
[28/Sep/2018 12:36:49] "GET /admin/cheque/mycheque/ HTTP/1.1" 500 301999
Exception Value:
Signs matching query does not exist.
DJANGO throw these two errors how could i resolve this to show the relation between user and these details
models.py
class Signs(models.Model):
relation = models.OneToOneField(User, on_delete=models.CASCADE)
department = models.CharField(null=True, blank=True, max_length=1000, help_text="User Department")
mobile = models.CharField(null=True, blank=True, max_length=1000, help_text="User Mobile")
def __str__(self):
return (self.relation.username).title()
class Meta:
verbose_name_plural = "Registration Information"
class Mycheque(models.Model):
related = models.ForeignKey(Signs, on_delete=models.CASCADE, null=True, blank=True)
to_pay = models.CharField(max_length=250, null=True, blank=True)
amount = models.BigIntegerField(default=0, null=True, blank=True)
amount_in_words = models.CharField(max_length=10000, null=True, blank=True)
vouchar_no = models.BigIntegerField(default=0, null=True, blank=True)
dated = models.DateTimeField(auto_now_add=True, null=True, blank=True)
cheque_no = models.BigIntegerField(default=0, null=True, blank=True)
cheque_date = models.CharField(max_length=10, null=True, blank=True)
account_no = models.BigIntegerField(default=0, null=True, blank=True)
def save(self):
self.dated = datetime.now()
super(Mycheque, self).save()
def __str__(self):
return (self.related.relation.username).title()
class Meta:
verbose_name_plural = "Single Cheque Of Users"
views.py
def mycheque(request):
if request.method == "POST":
userdata = User.objects.get(username = request.user)
user_data = Signs.objects.get(relation_id=userdata.id)
if userdata.check_password(passwd) == True:
to_p = request.POST['topay']
amnt = request.POST['amount1']
amnt_in_words = request.POST['amount_string']
vouch_no = request.POST['voucharno']
d = request.POST['date']
cheq_no = request.POST['chequeno']
cheq_date = request.POST['chequedate']
acc_no = request.POST['accountno']
single = Mycheque(to_pay=to_p, amount=amnt, amount_in_words=amnt_in_words, vouchar_no=vouch_no, dated=d, cheque_no=cheq_no, cheque_date=cheq_date, account_no=acc_no)
single.save()
messages.success(request, "Your Cheque is created")
else:
messages.error(request, "Please Try again...")
return render(request, 'cheque/mycheque.html', {})
AttributeError
The error originates from the fact that the Mycheque.related relation is NULLable: it is possible to set it to NULL.
This means that for some Mycheques, the self.related object will be None, and therefore self.related.relation will error.
You will thus to add some logic to the __str__ to handle that case, for example:
def __str__(self):
if self.related:
return self.related.relation.username.title()
else:
return 'no related!'
That being said, I find the __str__ function rather "weird": the textual representation of a Mycheque only takes the related.relation object into account? So you should consider redesigning this.
The same "scanario" can happen on multiple places, so you should run a search and fix it accordingly
Singns does not exists
This is probably due to the line:
user_data = Signs.objects.get(relation_id=userdata.id)
The strange thing is here that you do nothing with the user_data variable at all, so - given you implemented the view correctly - can remove the line.
That being said the line itself makes no sense: you query a ForeignKey in reverse, that means that for a User object, there can be zero, one, or multiple Signs objects. So it makes more sense to use .filter(..) since if there are no related Signs (or multiple) this query will error.
Refactoring the view, and introducing a ModelForm
The line above:
userdata = User.objects.get(username = request.user)
makes no sense either: the request.user is a User object, so it is equivalent to:
userdata = request.user
Other piculiarities in the view are that you perform authentication yourself, instead of using the #login_required [Django-doc], and furthermore it here definitely makes sense to define a ModelForm [Django-doc], by using a form, the view will probably be reduced to 5-7 lines, and the errors are handled at the correct place.

How can I find out why a Django database .save() failed?

My Django database .save() is throwing an exception. Is there a way to find out why?
My main code is:
for i in data['movies']:
try:
id = i['regions'][0]['products'][0]['product_id']
title = i['regions'][0]['products'][0]['product_title']
m = Movie.objects.get(preview_id=id)
except Movie.DoesNotExist:
try:
movie = Movie()
movie.preview_id = id
movie.title = title
movie.details = i['regions'][0]['products'][0]['description']
movie.date = i['regions'][0]['products'][0]['upd_date']
movie.trailer = i['regions'][0]['products'][0]['clips'][0]['files'][0]['url']
movie.likes = 0
movie.dislikes = 0
movie.save() # THIS IS THROWING AN ERROR
print id + ": " + title
for genre in i['regions'][0]['categories']:
try:
g = Genre.objects.get(title__exact=genre['name'])
movie.genres.add(g)
except Genre.DoesNotExist:
g = Genre(title=genre['name'])
g.save()
movie.genres.add(g)
for pic in i['regions'][0]['pictures']:
if pic['type_name'] == "poster_large":
movie.picture = pic['url']
movie.save()
except:
print 'error'
print 'Success'
My movies model looks like this:
class Movie(models.Model):
# Movie Model store all the detail of a movie
# Each movie is created by a User
user = models.ForeignKey(User)
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, blank=True, null=True)
details = models.TextField()
picture = models.ImageField(upload_to=filename, blank=True, null=True)
trailer = models.FileField(upload_to=videoname, blank=True, null=True)
# genres = ManyToMany with Genre Model
genres = models.ManyToManyField(Genre, related_name='genres')
likes = models.BigIntegerField(blank=True, null=True)
dislikes = models.BigIntegerField(blank=True, null=True)
trigahs = models.BigIntegerField(blank=True, null=True)
# Director with People Model
director = models.ForeignKey(People, blank=True, null=True)
# Casts = ManyToMany with People Model
casts = models.ManyToManyField(People, related_name='casts', blank=True, null=True)
date = models.DateField(blank=True, null=True)
amazon_id = models.CharField(max_length=200, blank=True, null=True)
preview_id = models.BigIntegerField(blank=True, null=True)
created_on = models.DateTimeField(auto_now_add=True)
def add_likes(self):
self.likes = self.likes + 1
self.save()
def add_dislikes(self):
self.dislikes = self.dislikes + 1
self.save()
def save(self):
super(Movie, self).save()
if not self.slug:
self.slug = '%s' % (
slugify(self.title)
)
super(Movie, self).save()
And the super function is defined as follows:
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
"""
Saves the current instance. Override this in a subclass if you want to
control the saving process.
The 'force_insert' and 'force_update' parameters can be used to insist
that the "save" must be an SQL insert or update (or equivalent for
non-SQL backends), respectively. Normally, they should not be set.
"""
# Ensure that a model instance without a PK hasn't been assigned to
# a ForeignKey or OneToOneField on this model. If the field is
# nullable, allowing the save() would result in silent data loss.
for field in self._meta.concrete_fields:
if field.is_relation:
# If the related field isn't cached, then an instance hasn't
# been assigned and there's no need to worry about this check.
try:
getattr(self, field.get_cache_name())
except AttributeError:
continue
obj = getattr(self, field.name, None)
# A pk may have been assigned manually to a model instance not
# saved to the database (or auto-generated in a case like
# UUIDField), but we allow the save to proceed and rely on the
# database to raise an IntegrityError if applicable. If
# constraints aren't supported by the database, there's the
# unavoidable risk of data corruption.
if obj and obj.pk is None:
raise ValueError(
"save() prohibited to prevent data loss due to "
"unsaved related object '%s'." % field.name
)
using = using or router.db_for_write(self.__class__, instance=self)
if force_insert and (force_update or update_fields):
raise ValueError("Cannot force both insert and updating in model saving.")
if update_fields is not None:
# If update_fields is empty, skip the save. We do also check for
# no-op saves later on for inheritance cases. This bailout is
# still needed for skipping signal sending.
if len(update_fields) == 0:
return
update_fields = frozenset(update_fields)
field_names = set()
for field in self._meta.fields:
if not field.primary_key:
field_names.add(field.name)
if field.name != field.attname:
field_names.add(field.attname)
non_model_fields = update_fields.difference(field_names)
if non_model_fields:
raise ValueError("The following fields do not exist in this "
"model or are m2m fields: %s"
% ', '.join(non_model_fields))
# If saving to the same database, and this model is deferred, then
# automatically do a "update_fields" save on the loaded fields.
elif not force_insert and self._deferred and using == self._state.db:
field_names = set()
for field in self._meta.concrete_fields:
if not field.primary_key and not hasattr(field, 'through'):
field_names.add(field.attname)
deferred_fields = [
f.attname for f in self._meta.fields
if (f.attname not in self.__dict__ and
isinstance(self.__class__.__dict__[f.attname], DeferredAttribute))
]
loaded_fields = field_names.difference(deferred_fields)
if loaded_fields:
update_fields = frozenset(loaded_fields)
self.save_base(using=using, force_insert=force_insert,
force_update=force_update, update_fields=update_fields)
save.alters_data = True
This is all code I am taking over so much of it is a mystery to me. Apologies if this question is not properly framed. But could really do with a pointer as to how to track down the reason for failure.
try:
line 1
line 2
except Exception as e:
print e
This will reveal the error. This is only for debugging. You should properly handle the exceptions.

Django ModelForm - conditional validation

I need to add a conditional piece of validation to my ModelForm.
Below is my Listing Model.
LISTING_TYPES = (
('event', 'event'),
('release', 'release')
)
class Listing(models.Model):
title = models.CharField(max_length=255, verbose_name='Listing Title')
type = models.CharField(max_length=255, choices=LISTING_TYPES, verbose_name='Listing Type')
slug = models.SlugField(max_length=100)
content = models.TextField(verbose_name='Listing Overview')
competition = models.TextField()
date_start = models.DateTimeField()
time_start = models.CharField(max_length=255)
date_end = models.DateTimeField()
time_end = models.CharField(max_length=255)
pub_date = models.DateTimeField('date published', auto_now_add=True)
venue = models.ForeignKey(Venue)
class ListingForm(ModelForm):
date_start = forms.DateField(input_formats=DATE_INPUT_FORMATS)
date_end = forms.DateField(input_formats=DATE_INPUT_FORMATS)
class Meta:
model = Listing
Venue should only be required if type == 'event'. If type == 'release', I want venue to be required=False
How can I go about this?
Thanks
First Listing.venue needs to allow null values
venue = models.ForeignKey(Venue, blank=True, null=True)
Your ModelForm then needs a clean method. Something like the following
def clean(self):
cleaned_data = super(ListingForm, self).clean()
venue = cleaned_data.get("venue")
type = cleaned_data.get("type")
if type == 'event' and not venue:
raise forms.ValidationError("A venue is required for events")
You mentioned doing ModelForm validation, but you should ask yourself if this rule is specific to creating objects with forms, or whether it is inherent in your data model itself. If it's the latter, then doing model validation makes more sense.
from django.core.exceptions import ValidationError
class Listing(models.Model):
...
def clean(self):
super(Listing, self).clean()
if self.type == 'event' and not self.venue:
raise ValidationError('A venue is required for events')
This will be called during ModelForm validation, so it will have the same effect there, but defining it on the model allows you to check the consistency of your data at any point with the Model.full_clean() method.
As Iain points out, you first need to allow null values for venue.
venue = models.ForeignKey(Venue, blank=True, null=True)

Django, want to upload either image (ImageField) or file (FileField)

I have a form in my html page, that prompts user to upload File or Image to the server. I want to be able to upload ether file or image. Let's say if user choose file, image should be null, and vice verso. Right now I can only upload both of them, without error. But If I choose to upload only one of them (let's say I choose image) I will get an error:
"Key 'attachment' not found in <MultiValueDict: {u'image': [<InMemoryUploadedFile: police.jpg (image/jpeg)>]}>"
models.py:
#Description of the file
class FileDescription(models.Model):
TYPE_CHOICES = (
('homework', 'Homework'),
('class', 'Class Papers'),
('random', 'Random Papers')
)
subject = models.ForeignKey('Subjects', null=True, blank=True)
subject_name = models.CharField(max_length=100, unique=False)
category = models.CharField(max_length=100, unique=False, blank=True, null=True)
file_type = models.CharField(max_length=100, choices=TYPE_CHOICES, unique=False)
file_uploaded_by = models.CharField(max_length=100, unique=False)
file_name = models.CharField(max_length=100, unique=False)
file_description = models.TextField(unique=False, blank=True, null=True)
file_creation_time = models.DateTimeField(editable=False)
file_modified_time = models.DateTimeField()
attachment = models.FileField(upload_to='files', blank=True, null=True, max_length=255)
image = models.ImageField(upload_to='files', blank=True, null=True, max_length=255)
def __unicode__(self):
return u'%s' % (self.file_name)
def get_fields(self):
return [(field, field.value_to_string(self)) for field in FileDescription._meta.fields]
def filename(self):
return os.path.basename(self.image.name)
def category_update(self):
category = self.file_name
return category
def save(self, *args, **kwargs):
if self.category is None:
self.category = FileDescription.category_update(self)
for field in self._meta.fields:
if field.name == 'image' or field.name == 'attachment':
field.upload_to = 'files/%s/%s/' % (self.file_uploaded_by, self.file_type)
if not self.id:
self.file_creation_time = datetime.now()
self.file_modified_time = datetime.now()
super(FileDescription, self).save(*args, **kwargs)
forms.py
class ContentForm(forms.ModelForm):
file_name =forms.CharField(max_length=255, widget=forms.TextInput(attrs={'size':20}))
file_description = forms.CharField(widget=forms.Textarea(attrs={'rows':4, 'cols':25}))
class Meta:
model = FileDescription
exclude = ('subject',
'subject_name',
'file_uploaded_by',
'file_creation_time',
'file_modified_time',
'vote')
def clean_file_name(self):
name = self.cleaned_data['file_name']
# check the length of the file name
if len(name) < 2:
raise forms.ValidationError('File name is too short')
# check if file with same name is already exists
if FileDescription.objects.filter(file_name = name).exists():
raise forms.ValidationError('File with this name already exists')
else:
return name
views.py
if request.method == "POST":
if "upload-b" in request.POST:
form = ContentForm(request.POST, request.FILES, instance=subject_id)
if form.is_valid(): # need to add some clean functions
# handle_uploaded_file(request.FILES['attachment'],
# request.user.username,
# request.POST['file_type'])
form.save()
up_f = FileDescription.objects.get_or_create(
subject=subject_id,
subject_name=subject_name,
category = request.POST['category'],
file_type=request.POST['file_type'],
file_uploaded_by = username,
file_name=form.cleaned_data['file_name'],
file_description=request.POST['file_description'],
image = request.FILES['image'],
attachment = request.FILES['attachment'],
)
return HttpResponseRedirect(".")
Let's say if user choose file, image should be null, and vice verso.
You could:
make an SQL constraint,
override model.save() to fail if either file or image is blank,
define ContentForm.clean() to raise a ValidationError if either file or image is blank, see Cleaning and validating fields that depend on each other.
Also be careful that up_f will be a tuple in:
up_f = FileDescription.objects.get_or_create(
I've had the same problem. For some reason the key value dict only takes one key pair values. save the the attachment like so
attachment=form.data['attachment']
as opposed to
attachment=request.FILES['attachment']
it should run, but curious if it will save as a file.
i know this question is old but had a difficult time with this same issue
Create radio buttons for the user to chose what he/she want to upload and use only one FileField attribute in the model. You can convert the other field to BooleanField or CharField to indicate what the user selected.