Unique constraint failed using admin site

I would like to fill in the “author” field in a FORM well before the SAVE_MODEL starts.
So when the user get the EntryForm it has to has predefined data in it.
I use two keys “author” and “title”.

My problem is when I enter the same title using same user on second time, I get error message on SAVE_MODEL.

Exception Value: UNIQUE constraint failed: blog_entry.author_id, blog_entry.title
It seems the “EntryForm” will not fill in the “author” fields

Also, when I debug it, it does not stop at clean_author() or clean_last_modified_by() functions in FORMS.PY.

May I ask your help, please?

Models.py

from django.db import models
import datetime

from django.contrib.auth.models import User
# Create your models here.

class Entry(models.Model):
    title = models.CharField(max_length=250)
    slug = models.SlugField()
    pub_date = models.DateField(default=datetime.datetime.today)
    author = models.ForeignKey(
        User, 
        on_delete=models.CASCADE,
        related_name='entries', 
        blank=True)
    body = models.TextField()
    last_modified = models.DateTimeField(auto_now=True)
    last_modified_by = models.ForeignKey(
        User, 
        on_delete=models.CASCADE,
        related_name='entry_modifiers',
        blank=True)

    class Meta:
        # unique_together = ('author', 'title')
        constraints = [
            models.UniqueConstraint(fields=['author', 'title' ], name ='unique_title_by_user')
        ]

forms.py

from django.contrib.auth.models import User
from django import forms

from blog.models import *


class EntryForm(forms.ModelForm):
    class Meta:
        model = Entry
        fields = '__all__'
    
    def clean_author(self):
        if not self.cleaned_data['author']:
            return User()
        return self.cleaned_data['author']
    
    def clean_last_modified_by(self):
        if not self.cleaned_data['last_modified_by']:
            return User()
        return self.cleaned_data['last_modified_by']

admin.py

from django.contrib import admin

from blog.forms import *

# Register your models here.
class EntryAdmin(admin.ModelAdmin):
    form = EntryForm
    
    list_display = ('title', 'pub_date', 'author')
    prepopulated_fields = { 'slug': ['title'] }
    readonly_fields = ('last_modified', 'last_modified_by',)
    fieldsets = ((
        None, {
            'fields': ('title', 'body', 'pub_date')
        }), (
        'Other Information', {
            'fields': ('last_modified', 'last_modified_by', 'slug'),
            'classes': ('collapse',)
        })
    )
    
    def save_model(self, request, obj, form, change):
        # if not obj.author.id:
        if not obj.id:
            obj.author = request.user
        obj.last_modified_by = request.user
        obj.save()


admin.site.register(Entry, EntryAdmin)

To answer your primary question:

Is directly caused by this:

You’re explicitly saying that it’s not valid to have two rows with the same author and title.

Hello Ken,
Thank you for taking your time to answer my question.
It was my fault, I did not tell that “author” and “title” has to be unique and the program has to manage or handle do not save same records two times.
Is there a user friendly way to let the user knows the “title” was already entered before the save_model (Question 1 ) or during the save_model (Questions 2)?

My third question is it seems the program never reaches the clean_author(self) and clean_last_modified_by(self) functions. I debugged it several times but these functions never called. What did I do wrongly? When and how the program reaches these two functions?

I use Django Admin only and I have no HTML or VIEWS.PY program, setup

I appreciate if you check my program.

Thank you,
Laci

The last_modified_by field is readonly, and readonly fields aren’t cleaned - they don’t need to be because they’re not edited.

Also, I don’t see the author field in your fieldsets, which means it wouldn’t be an editable field either.

You also have a couple problems here:

The fields author and last_modified_by are foreign keys to the User table. If those fields aren’t supplied, you’re creating a new “User” object by use of the bare constructor. That new User object does not exist in the database, and so it can’t be used as the target of a foreign key.

Also see the docs for save_model for proper use of that method.

Thank you, you are right.
I modified fields to have ‘author’ and I put it in readonly_fields as well because I do not want user to fill in. It should be filled in automatically.

I have read the save_model documentation.
I have already use it in my example, at my first message there is and admin.py included the save_model.

I did not describe what I would like to achieve.
I would like to fill in automatically the ‘author’ field when the form is opened or after it but definitely well before SAVE.

When user enter the already existing ‘title’ and what’s to SAVE is in Django Admin, I would like to warm user the title already exists in user friendly way.

In case the ‘author’ fields will be fillede in by save_model() it gives unique error and I do not know how to manage this error.

To summarize my problem:
- (P1) I do not know how to fill in ‘author’ field automatically with current user
- (P2) Before save, check if the new records (key: ‘author’ and ‘title’) already exists in database
I would like to inform your when the record exists
- (P3) in model_save() is there any way to manage error (unique constraint) and let the customer knows about the record already exist ?

Thank you,
Laci

Your save_model does not match what’s shown in the example - look more carefully. Also notice how it precisely answers your first question.

Rather than trying to use the clean_<field> method, use the clean method. You then can raise a ValidationError to show that an error condition exists. (Answering P2.)
Also, waiting until model_save to handle that error is basically too late in the process. Perform your test in the clean method and raise the ValidationError there. (P3)

Ken

Hello Ken,

Thank you very much to spend your time with my request.
I am sorry for asking too much but I can do small steps like baby because I have just startedd to learn Django.

save_model()., I changed this obj.save() to this super().save_model(request, obj, form, change)
I hope this line was what you were referring to.

I had problem during clean() method because I had no idea how to get the user ID (who already had logged in Django Admin).
I solved it this way:
admin.py

def get_form(self, request, *args, **kwargs):
        form = super(EntryAdmin, self).get_form(request, *args, **kwargs)
        form.request = request
        return form

    def save_model(self, request, obj, form, change):
        # if not obj.author.id:
        if not obj.id:
            obj.author = request.user
        obj.last_modified_by = request.user
        # obj.save()
        super().save_model(request, obj, form, change)

forms.py

def clean(self):
        cleaned_data = super().clean()
        # author here is None
        author = cleaned_data.get("author")
        title = cleaned_data.get("title")
        if not author:
            try:
                author = self.request.user
                Entry.objects.get(author= author, title= title)
            except Entry.DoesNotExist:
                pass
            else:
                raise forms.ValidationError(_('This Title with Author already exists.'))

This way works fine. Now the program give warning if the user whats to enter duplicated data (‘author’ and ‘title’)
I think this is not the best solution and you know much better way…

I appreciate if you review my code.

Thanks,
Laci

Having it working, and you understanding the code is more important right now than any “style” considerations I may raise.

I will mention a couple minor items:

  • Readonly fields don’t appear in cleaned_data. If author is a readonly field, you’ll never get a value from cleaned_data.get('author') - there’s no value in performing the get and the subsequent if statement.

  • You have other options for checking the existence of a matching value. See the api docs for exists.

The combination of these two items allows you to simplify your clean method to look something like this:

def clean(self):
    cleaned_data = super().clean()
    title = cleaned_data.get("title")
    if Entry.objects.filter(author=self.request.user, title=title):
        raise forms.ValidationError(_('This Title with Author already exists.'))
1 Like

Hello Ken,

Thank you and thank you. :slight_smile:
This was done very well, with your support. I anjoy it.

I think I will bother you again soon with different issues…

Is there anyway to support/donate you and Django Community?
Do you have PayPal account? Could you please let me know?

Ps: this is my first issue. Could please, let me know how to close this issue?
Do I have to do something with this issue?

Thank you,
Laci

That’s why we’re here!

Me? No. The Django Community? Several ways - Start with taking a look at Support Django. I wholeheartedly support all the initiatives on that page. Take your pick as you see fit.

Technically, these discussions aren’t “issues”. This is a forum to exchange information and ideas - so there’s nothing really to “close”. (I think there’s a “solution” checkbox that shows up at the bottom of the replies - you can select a reply as the solution to your specific question. That may help other people browsing through the topics see that a particular question was answered to the satisfaction of the person asking it.)

Ken

1 Like