Django regex field - unique and without blank spaces - django

I have a ModelForm, in which I'm having a CharField, which is declared as unique in the Model.
But I have 2 problems:
If I fill in the form with a field having the same name I don't get an error message.
I'd like this field not to contain white spaces.
Is it possible to do that using a ModelForm?

You can do something close to this:
class MyModelForm(forms.ModelForm):
# your field definitions go here
def clean_myuniquefield(self):
# strip all spaces
data = str(self.cleaned_data['myuniquefield']).replace(' ', '')
model = self._meta.model
# check if entry already exists
try:
obj = model.objects.get(myuniquefield=data)
except model.DoesNotExist:
return data
raise forms.ValidationError("Value already exists!")

To get rid of spaces, make a clean_fieldname function to strip the spaces.
http://docs.djangoproject.com/en/dev/ref/forms/validation/#ref-forms-validation
As for uniqueness, also note about the meta-field unique_together. I don't know if you need it, but I didn't know about it until I dug around.
If you really need to do uniqueness checking before trying to add and failing, you can also do that in the clean_* function. However, it might be better to assume that the database will take care of it and fail in a standard way, and just set up your error messages properly. That way, if you change constraints later, it will flow through more easily. And if others have to maintain your code, it will be more standard.
Hope this helps.

Related

Django ignores model field

So, I came across this when I was working on a project.
I had mistakenly placed a "," after a field in one of my models and Django did all the migrations while ignoring that particular field. It took me a while to realize that a little "," after the field is responsible for my field not being reflected in the database.
However, I understand that there shouldn't be a coma but I was kind of expecting Django to give me an error or at least a warning.
Something like maybe:
"Invalid syntax in models.py near FieldName"
EDIT:
"one or more model fields are stored as tuple/s are you sure you want to do so?"
But it ignores that particular field and keeps on migrating. My question is why does Django let that happen? Is this the expected behaviour and shouldn't Django notify for such things? or why this is being passed silently.
Here is an example to have a look at.
class person(models.Model):
name = models.CharField(max_length=10)
surname = models.CharField(max_length=10),
age = models.PositiveIntegerField()
Now, if you create migrations and apply them Django will simply ignore the surname field here and apply the migrations without any errors, why is it so?
It is not invalid syntax. By adding a trailing comma, you wrap the field in a singleton tuple. So the type of person.surname is tuple.
For example if you write:
>>> a = 1,
>>> a
(1,)
>>> type(a)
<class 'tuple'>
A model can, besides the model fields contain all sorts of things: constants, subclasses, methods, etc.
One could do an exhaustive search in all the fields, etc. to check if a tuple wraps a model field, but that could take considerable time, it might result in evaluating lazy attributes, and it might even get stuck in an infinite loop.
It might however be something that can be added to flake8-django [GitHub].

Django GROUP BY including unnecessary columns?

I have Django code as follows
qs = Result.objects.only('time')
qs = qs.filter(organisation_id=1)
qs = qs.annotate(Count('id'))
And it gets translated into the following SQL:
SELECT "myapp_result"."id", "myapp_result"."time", COUNT("myapp_result"."id") AS "id__count" FROM "myapp_result" WHERE "myapp_result"."organisation_id" = 1 GROUP BY "myapp_result"."id", "myapp_result"."organisation_id", "myapp_result"."subject_id", "myapp_result"."device_id", "myapp_result"."time", "myapp_result"."tester_id", "myapp_result"."data"
As you can see, the GROUP BY clause starts with the field I intended (id) but then it goes on to list all the other fields as well. Is there any way I can persuade Django not to specify all the individual fields like this?
As you can see, even with .only('time') that doesn't stop Django from listing all the other fields anyway, but only in this GROUP BY clause.
The reason I want to do this is to avoid the issue described here where PostgreSQL doesn't support annotation when there's a JSON field involved. I don't want to drop native JSON support (so I'm not actually using django-jsonfield). The query works just fine if I manually issue it without the reference to "myapp_result"."data" (the only JSON field on the model). So if I could just persuade Django not to refer to it, I'd be fine!
only only defers the loading of certain fields, i.e. it allows for lazy loading of big or unused fields. It should generally not be used unless you know exactly what you're doing and why you need it, as it is nothing more than a performance booster than often decreases performance with improper use.
What you're looking for is values() (or values_list()), which actually excludes certain fields instead of just lazy loading. This will return a dictionary (or list) instead of a model instance, but this is the only way to tell Django to not take other fields into account:
qs = (Result.objects.filter_by(organisation_id=1)
.values('time').annotate(Count('id')))

Django Models - Prepare the data for the database

I have a form that askes for a phone number. I need to make sure that only digits [0-9] get saved in the database.
In the Django documentation it says:
What happens when you save?
3) Prepare the data for the database. Each field is asked to provide its current value in a data type that can be written to the database.
How does this happen? Or more specifically, how can I make sure this is cleaned? I know that I can just override the models save method, but it seems like there is a better way and I'm just not sure how to do it.
I guess I could write a custom field for it, but that seems like overkill here.
Also, I realize that I can put the validation on the form, but it really feels like stripping out the characters belongs on the model.
Your question specifically about point 3 is a little different from "cleaning" in the way django uses the term.
3) Prepare the data for the database. Each field is asked to provide its current value in a data type that can be written to the database.
Point 3 is about converting the python object values to one suitable for a database. Specifically, this is done in Field.get_prep_value and Field.get_db_prep_value
https://docs.djangoproject.com/en/dev/howto/custom-model-fields/#django.db.models.Field.get_prep_value
It's the opposite of to_python which takes a DB value and converts it to a python object.
As for ensuring only digits 0-9 get stored, that would be done in a Fields clean method (subclass IntegerField), form clean method, form clean_FIELDNAME method, or model clean.
You can add a custom Form Cleaning method to your objects model - take a look at this article https://docs.djangoproject.com/en/dev/ref/forms/validation/#form-field-default-cleaning
Look at "Cleaning a specific field attribute"
use django model form + custom form field cleaning
Below is a quick example of what you might be looking for, where MyModel is the model containing the phone number field, which I named it tel here.
import re
class MyForm(ModelForm):
class Meta:
model = MyModel
def clean_tel(self):
tel = self.cleaned_data.get('tel', '') # this is from user input
# use regular expression to check if tel contains only digits; you might wanna enhance the regular expression to restrict the tel number to have certain number of digits.
result = re.match(r'\d+', tel)
if result:
return tel # tel is clean so return it
else:
raise ValidationError("Phone number contains invalid character.")

How to search for objects without certain tags?

I have a queryset containing some objects. Depending on some case or the other i now want to exclude all the objects without certain tags (_tags is the name of the TagField on my model):
self.queryset=self.queryset.exclude(_tags__id__in=avoid)
But this just leaves me with an error:
Caught FieldError while rendering:
Join on field '_tags' not permitted.
Did you misspell 'id' for the lookup type?
As i'm pretty sure i did not misspell 'id', i did some searching on how to use tagging for something like this. In the docs there is a lot about custom Managers, but somehow i just can't get it how i can use them to get what i want.
edit:
corrected the code above to
self.queryset=self.queryset.exclude(_tags__in=avoid)
where avoid is a list of integers. And that leaves me with the problem that the TagField of django-tagging is just a special CharField (or TextField?). Which will, of course, not sort out anything if i just query it against a list of integers. I could try to solve this in a way like this:
for tag in avoid:
self.queryset=self.queryset.exclude(_tags__contains=tag.name)
which is not only ugly, but also leaves me with the problem of tags made of multiple words or matching parts of other tags.
I somehow have the suspicion that this could be solved in a much prettier way by someone who has understood how django-tagging works.
How are your models defined? Is _tags a ForeignKey field?
if not remove the __id part
self.queryset=self.queryset.exclude(_tags__in=avoid)
Unfortunately, no, there's no prettier way. In fact, the actual solution is even uglier, but when all the tags are stored in a single text field, there's no other way:
from django.db.models import Q
startswith_tag = Q(_tags__startswith=tag.name+' ')
contains_tag = Q(_tags__contains=' '+tag.name+' ')
endswith_tag = Q(_tags__endswith=' '+tag.name)
self.queryset=self.queryset.exclude(startswith_tag | contains_tag | endswith_tag)
The code above assumes that tags are delimited with spaces. If not, you'll have to modify the code to match how they are delimited. The idea is that you use the delimiter as part of the search to ensure that it's the actual tag and not just part of another tag.
If you don't want to do it this way, I'd suggest switching to another tag system that doesn't dump them all into a single text field, django-taggit for instance.
As described in the comment on Chris' answer, django-tagging does not deliver the tagstring when accessing model._tag. In the end i had no other solution than to do the query and sort out the loops containing a certain tag afterwards:
itemlist = list(queryset)
avoid = some_list_of_tag_ids
# search for loops that have NONE of the avoid tags
for item in itemlist:
# has tags and [ if a tag.id in avoid this list has an element]
if (item.tags) and [tag for tag in item.tags if tag.id in avoid]:
# remove the item from the list
itemlist.remove(item)
To complete that the model for this looks like this:
class Item(models.Model):
_tags = TagField(blank=True,null=True)
def _get_tags(self):
return Tag.objects.get_for_object(self)
def _set_tags(self, tags):
Tag.objects.update_tags(tags)
tags = property(_get_tags, _set_tags)
Allthough i tried for quite a while, i found no way of chaining a query against tagging tags into a query chain for an object. For this project I'm stuck with tagging, but this is a real drawback...

Processing dynamic MultipleChoiceField in django

All the answers I've seen to this so far have confused me.
I've made a form that gets built dynamically depending on a parameter passed in, and questions stored in the database. This all works fine (note: it's not a ModelForm, just a Form).
Now I'm trying to save the user's responses. How can I iterate over their submitted data so I can save it?
The MultipleChoiceFields are confusing me especially. I'm defining them as:
self.fields['question_' + str(question.id)] = forms.MultipleChoiceField(
label=mark_safe(required_tag +
question.label + "<br/>Choose any of the following answers"),
help_text=question.description,
required=question.required,
choices=choices,
widget=widgets.CheckboxSelectMultiple())
When I select several options, the actual posted data is something like:
question_1=5&question_1=6
Will django automatically realise that these are both options on the same form and let me access an iterable somewhere? I was going to do something like:
for field in self.cleaned_data:
print field # save the user's response somehow
but this doesn't work since this will only return question_1 once, even though there were two submitted values.
Answer: The for loop now works as expected if I loop through self.fields instead of self.cleaned_data:
for field in self.fields:
print self.cleaned_data[field]
... this doesn't work ...
Are you sure? Have you tested it? Normally the cleaned_data value for a MultipleChoiceField is a list of the values chosen on the form.
So yes, it only returns question_1 once, but that returned value itself contains multiple values.