unique_together not working as expected in view - django

I added unique_together in the Django model. It works fine in the Django admin panel but doesn't work when I create model instances in a for loop in the Django view.
My Model:
class Parent(models.Model):
attribute_1 = models.CharField(max_length=100)
attribute_2 = models.IntegerField()
attribute_3 = models.CharField(max_length=100)
class Meta:
unique_together = (("attribute_1", "attribute_2"),)
View:
class ParentView(View):
template_name = "app/index.html"
def get(self, *args, **kwargs):
for i in range(10):
Parent.objects.create(
attribute_1=f"value-{i}",
attribute_2=i,
attribute_3=f"value-{i}",
)
return render(self.request, self.template_name)
Problem:
When I open this view, it creates the entries in the Parent model. when I refresh the page, I expect a unique value error but it doesn't give any error at all and creates entries again. No matter how much time I reload the page, it creates duplicate entries without giving an error.
What I tried?
I tried to change my DB from sqlite3 to PostgreSQL but it didn't work.
I tried to switch attributes but no luck.
It works perfectly fine on the admin panel.

It works perfectly fine on the admin panel.
That is because it is validated by a ModelForm, you can check this before saving with .full_clean(…) [Django-doc]:
class ParentView(View):
template_name = 'app/index.html'
def get(self, *args, **kwargs):
for i in range(10):
parent = Parent(
attribute_1=f'value-{i}',
attribute_2=i,
attribute_3=f'value-{i}',
)
parent.full_clean()
parent.save()
return render(self.request, self.template_name)
Most databases can enforce unique constraints, you will have to makemigrations and migrate.
Note: As the documentation on unique_together [Django-doc] says, the unique_together constraint will likely become deprecated. The documentation advises to use the UniqueConstraint [Django-doc] from Django's constraint
framework.

Related

Add method doesnt work when trying to establish m2m relationships using post_save in Django

My Content model has a many-to-many relationship to the Tag model. When I save a Content object, I want to add the relationships dynamically. I'm doing this the following way.
def tag_content(obj):
for tag in Tag.objects.all():
print tag
obj.tags.add(tag)
obj.is_tagged = True
obj.save()
class Tag(models.Model):
name = models.CharField(max_length=255)
class Content(models.Model):
title = models.CharField(max_length=255)
is_tagged = models.BooleanField(default=False)
tags = models.ManyToManyField(Tag, blank=True)
def save(self, *args, **kwargs):
super(Content, self).save(*args, **kwargs)
#receiver(post_save, sender = Content)
def update_m2m_relationships_on_save(sender, **kwargs):
if not kwargs['instance'].is_tagged:
tag_content(kwargs['instance'])
The tag_content function runs, however, the m2m relationships are not established. Im using Django 1.9.8 btw. This makes no sense. What am I missing? Moreover, if I do something like tag_content(content_instance) in shell, then the tags are set, so the function is ok. I guess the problem is in the receiver. Any help?
Edit
My question has nothing to do with m2m_changed, as I have said, creating a Content object in shell works perfectly. Therefore, the problem lies in the admin panel's setup.
Ok so I solved the problem. Basically, this has something to do with how Django handles its form in the admin panel. When trying to add the Contents from admin, I kept the tags field empty, thinking the tag_content function would handle it. However, that is exactly where the problem was, as creating a Content from shell tagged it just fine. In other words, changing the admin panel to something like this solved my problem :
from django.contrib import admin
from myapp.models import *
from django import forms
class ContentCreationForm(forms.ModelForm):
class Meta:
model = Content
fields = ('title',)
class ContentChangeForm(forms.ModelForm):
class Meta:
model = Content
fields = ('title', 'is_tagged', 'tags')
class ContentAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
if obj is None:
return ContentCreationForm
else:
return ContentChangeForm
admin.site.register(Tag)
admin.site.register(Content, ContentAdmin)
When trying to create a new Content, only the 'title' field is presented. This solves the problem.

Django: Multiple Databases, Routers and Test Framework

I'm currently writing the tests for our django application.
Sadly we had to use a multiple database layout and can't change this. (Distributed databases with multiple backends on different servers across multiple datacenters)
We have two databases:
default databases with django default tables
application databases with some models
For these models we wrote different routers like documented on the django site.
Now the problem, if I run python manage.py test customerreceipts the test framework dies after some seconds with the following error:
django.db.utils.ProgrammingError: relation "auth_user" does not exist
I checked the created database and there were no tables. Because of this a query from a model throws the error.
The problem model is (in database 2):
class CustomerReceipts(models.Model):
def _choices_user():
users = User.objects.all()
users = users.order_by('id')
return [(e.id, e.username) for e in users]
# General
receipt_name = models.CharField(max_length=20, verbose_name="Receipt name") #: Receipt name
....
# Auditing
owner = models.IntegerField(verbose_name="Owner", choices=[('', '')] + _choices_user())
Because multiple database setup does not support direct links, we use an IntegerField for the owner and the business logic handles the integrity.
The problem is the _choices_user() which sets up an query for the missing table. What I don't understand is why django does not create the table auth_user in the first run. If I remove the app with the causing model, the test framework is working without any problem.
Any ideas how this can be fixed?
Thanks!
Edit:
I created a one database setup and tried the same thing. Sadly it throws the same error!
I'm confused now. Can someone test this too? Create a model with _choices_user method and run test.
You can manually select the database. Just call using(). Method using() takes a single argument: the alias of the database on which you want to run the query.
def _choices_user():
users = User.objects.using('default').all()
.....
.....
Django 1.7 docs
This is not exactly a perfect answer but the only way currently:
Model (removed choices):
class CustomerReceipts(models.Model):
# General
receipt_name = models.CharField(max_length=20, verbose_name="Receipt name") #: Receipt name
....
# Auditing
owner = models.IntegerField(verbose_name="Owner")
Admin:
class CustomerReceiptsAdminForm(forms.ModelForm):
class Meta:
model = CustomerReceipts
users = forms.ChoiceField()
def __init__(self, *args, **kwargs):
super(CustomerReceiptsAdminForm, self).__init__(*args, **kwargs)
owner = self.instance.owner
usersAll = User.objects.all()
usersAll = usersAll.order_by('id')
available_choices = [(e.id, e.username) for e in usersAll]
self.fields['users'].choices = available_choices
self.fields['users'].initial = owner
class CustomerReceipts(admin.ModelAdmin):
fields = ('abc', 'users')
list_display = ('abc', 'get_user')
form = CustomerReceiptsAdminForm
def save_model(self, request, obj, form, change):
obj.owner = form.cleaned_data['users']
obj.save()
def get_user(self, obj):
return User.objects.get(id=obj.owner).username
get_user.short_description = 'User'
This will handle all displaying in the admin view and selects the right customer upon editing.

ForeignKey field will not appear in Django admin site

A foreign key on a model is not appearing in the Django admin site. This is irrespective of whether the field is explicitly specified in a ModelAdmin instance (fields = ('title', 'field-that-does-not-show-up')) or not.
I realize there are many variables that could be causing this behavior.
class AdvertiserAdmin(admin.ModelAdmin):
search_fields = ['company_name', 'website']
list_display = ['company_name', 'website', 'user']
class AdBaseAdmin(admin.ModelAdmin):
list_display = ['title', 'url', 'advertiser', 'since', 'updated', 'enabled']
list_filter = ['updated', 'enabled', 'since', 'updated', 'zone']
search_fields = ['title', 'url']
The problem is the advertiser foreign key is not showing up in the admin for AdBase
class Advertiser(models.Model):
""" A Model for our Advertiser
"""
company_name = models.CharField(max_length=255)
website = models.URLField(verify_exists=True)
user = models.ForeignKey(User)
def __unicode__(self):
return "%s" % self.company_name
def get_website_url(self):
return "%s" % self.website
class AdBase(models.Model):
"""
This is our base model, from which all ads will inherit.
The manager methods for this model will determine which ads to
display return etc.
"""
title = models.CharField(max_length=255)
url = models.URLField(verify_exists=True)
enabled = models.BooleanField(default=False)
since = models.DateTimeField(default=datetime.now)
expires_on=models.DateTimeField(_('Expires on'), blank=True, null=True)
updated = models.DateTimeField(editable=False)
# Relations
advertiser = models.ForeignKey(Advertiser)
category = models.ForeignKey(AdCategory)
zone = models.ForeignKey(AdZone)
# Our Custom Manager
objects = AdManager()
def __unicode__(self):
return "%s" % self.title
#models.permalink
def get_absolute_url(self):
return ('adzone_ad_view', [self.id])
def save(self, *args, **kwargs):
self.updated = datetime.now()
super(AdBase, self).save(*args, **kwargs)
def impressions(self, start=None, end=None):
if start is not None:
start_q=models.Q(impression_date__gte=start)
else:
start_q=models.Q()
if end is not None:
end_q=models.Q(impression_date__lte=end)
else:
end_q=models.Q()
return self.adimpression_set.filter(start_q & end_q).count()
def clicks(self, start=None, end=None):
if start is not None:
start_q=models.Q(click_date__gte=start)
else:
start_q=models.Q()
if end is not None:
end_q=models.Q(click_date__lte=end)
else:
end_q=models.Q()
return self.adclick_set.filter(start_q & end_q).count()
class BannerAd(AdBase):
""" A standard banner Ad """
content = models.ImageField(upload_to="adzone/bannerads/")
The mystery deepens. I just tried to create a ModelForm object for both AdBase and BannerAd, and both generated fields for the advertiser. Some crazy admin things going on here...
I believe I've just run into exactly the same problem, but was able to debug it thanks to the help of persistent co-workers. :)
In short, if you look in the raw HTML source you'll find the field was always there - it's just that:
Django tries to be clever and put the form field inside a div with CSS class="form-row $FIELD_NAME",
The field's name was "advertiser", so the CSS class was "form-row advertiser",
...Adblock Plus.
Adblock Plus will hide anything with the CSS class "advertiser", along with a hell of a lot of other CSS classes.
I consider this a bug in Django.
maybe it is an encode error. I had the same problem, but when i added # -- coding: UTF-8 -- in the models.py, all fine.
Another very dumb cause of the same problem:
If there is only one instance of the related model, then the filter simply won't show. There is a has_output() method in RelatedFieldListFilter class that returns False in this case.
It's a strange problem for sure. On the AdBase model if you change
advertiser = models.ForeignKey(Advertiser)
to
adver = models.ForeignKey(Advertiser)
then I believe it'll show up.
Powellc, do you have the models registered with their respective ModelAdmin class?
admin.site.register(Advertiser, AdvertiserAdmin) after the ModelAdmin definitions.
You are talking about the list_display option, right?
Is the unicode-method for your related model set?
If the field is a ForeignKey, Django
will display the unicode() of the
related object
Also check this thread for some hints: Can "list_display" in a Django ModelAdmin display attributes of ForeignKey fields?
Try disabling your ad blocker. No, this is not a joke. I just ran into this exact problem.
We just ran into this problem.
It seems that if you call you field advertiser the in the admin the gets given an 'advertiser' class.
Then is then hidden by standard ad blocking plugins. If you view source your field will be there.

In django, how to limit choices of a foreignfield based on another field in the same model?

I have these models (I have limited the number of fields to just those needed)
class unit(models.Model):
name = models.CharField(max_length=200)
class project(models.Model):
name = models.CharField(max_length=200)
class location(address):
project = models.ForeignKey(project)
class project_unit(models.Model):
project = models.ForeignKey(project)
unit = models.ForeignKey(unit)
class location_unit(models.Model):
project = models.ForeignKey(project)
#Limit the selection of locations based on which project has been selected
location = models.ForeignKey(location)
#The same here for unit. But I have no idea how.
unit = models.ForeignKey(project_unit)
My newbie head just cannot grasp how to limit the two fields, location and unit, in the location_unit model to only show the choices which refers to the selected project in location_unit. Should I override the modelform and make a query there or can I use the limit_choices_to. Either way I have failed trying both
Edit: Just to clarify, I want this to happen in the Django Admin. I have also tried formfield_for_foreignkey, but still a no go for me.
EDIT 2:
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "unit":
kwargs["queryset"] = project_unit.objects.filter(project=1)
return db_field.formfield(**kwargs)
return super(location_unit_admin, self).formfield_for_foreignkey(db_field, request, **kwargs)
The above code snippet works. But of course I don't want the project to point to 1. How do I reference to the models project_id?
I tried this:
kwargs["queryset"] = project_unit.objects.filter(project=self.model.project.project_id)
But that doesn't work (actually I have tried a lot of variations, yes I am a django newbie)
This is the answer, it is brilliant: https://github.com/digi604/django-smart-selects
Your formfield_for_foreignkey looks like it might be a good direction, but you have to realize that the ModelAdmin (self) won't give you a specific instance. You'll have to derive that from the request (possibly a combination of django.core.urlresolvers.resolve and request.path)
If you only want this functionality in the admin (and not model validation in general), you can use a custom form with the model admin class:
forms.py:
from django import forms
from models import location_unit, location, project_unit
class LocationUnitForm(forms.ModelForm):
class Meta:
model = location_unit
def __init__(self, *args, **kwargs):
inst = kwargs.get('instance')
super(LocationUnitForm, self).__init__(*args, **kwargs)
if inst:
self.fields['location'].queryset = location.objects.filter(project=inst.project)
self.fields['unit'].queryset = project_unit.objects.filter(project=inst.project)
admin.py:
from django.contrib import admin
from models import location_unit
from forms import LocationUnitForm
class LocationUnitAdmin(admin.ModelAdmin):
form = LocationUnitForm
admin.site.register(location_unit, LocationUnitAdmin)
(Just wrote these on the fly with no testing, so no guarantee they'll work, but it should be close.)

Django admin handling one to many relation

I have a django model as following
class Project(models.Model)
name=models.CharField(max_length=200)
class Application(models.Model)
proj=models.ForeignKey(Project, null=True, blank=True)
I need to modify the admin form of the project to be able to assign multiple applications to the project, so in the admin.py I have created a ModelAdmin class for the project as following
class ProjectAdmin(ModelAdmin)
form=projectForm
project_apps=[]
and the project form as following
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
project_apps =forms.ModelMultipleChoiceField(queryset=Application.objects.all(),required=False,)
def __init__(self, *args, **kwargs):
super(ProjectForm, self).__init__(*args, **kwargs)
if self.instance.id is not None:
selected_items = [ values[0] for values in Application.objects.filter(project=self.instance) ]
self.fields['project_apps'].initial = selected_items
def save(self,commit=True):
super(ProjectForm,self).save(commit)
return self.instance
by doing this I have a multiple select in the create/edit project form.
what I need is to override the save method to save a reference for the project in the selected applications?
how can I get the selected applications ????
Not entirely sure what you're trying to do, but maybe this?
def save(self,commit=True):
kwargs.pop('commit') # We're overriding this with commit = False
super(ProjectForm,self).save(commit)
if self.instance:
for a in self.cleaned_data['project_apps']:
a.proj = self.instance
a.save()
return self.instance
Now, I can't remember if in this case, self.cleaned_data['project_apps'] will actually contain a list of Application objects or not. I suspect it will, but if not this function will take care of that:
def clean_project_apps(self):
app_list = self.cleaned_data['project_apps']
result = []
for a in app_list:
try:
result.append(Application.objects.get(pk=a)
except Application.DoesNotExist:
raise forms.ValidationError("Invalid application record") # to be safe
return result
All in all I think this form is a bad idea though, because basically what is happening here is you're displaying all of the application records which doesn't make sense, since most of them will be associated with other projects.
Oh oh oh!!! Just noticed you wanted this to show up in a Multiple Select list!
You're (probably) doing it wrong
A multiple select means this isn't a one-to-many relationship. It's a many-to-many relationship.
This is what you want to do, easy peasy, doesn't require any custom forms or anything.
class Project(models.Model)
name=models.CharField(max_length=200)
project_apps = models.ManyToMany('Application', null=True, blank=True)
class Application(models.Model)
# nothing here (NO foreign key, you want more than one App/Proj and vice versa)
Indicating that this is a many-to-many field in Project will automagically create the multiple select box in admin. Ta da!