UNIQUE constraint failed: members_activemember.member_id onetoonefield Django

Question:

I get this error message. I am not sure why? It says it’s coming after saving my form from the update function in my views.py file.

Integraty Error:

IntegrityError at /update/8
UNIQUE constraint failed: members_activemember.member_id
Request Method: POST
Request URL:    https://topxgym.pythonanywhere.com/update/8
Django Version: 4.1
Exception Type: IntegrityError
Exception Value:    
UNIQUE constraint failed: members_activemember.member_id
Exception Location: /home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/db/backends/sqlite3/base.py, line 357, in execute
Raised during:  members.views.update
Python Executable:  /usr/local/bin/uwsgi
Python Version: 3.9.5
Python Path:    
['/var/www',
 '.',
 '',
 '/var/www',
 '/usr/local/lib/python39.zip',
 '/usr/local/lib/python3.9',
 '/usr/local/lib/python3.9/lib-dynload',
 '/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages',
 '/home/topxgym/.virtualenvs/env/topxgym']
Server time:    Tue, 23 Aug 2022 00:55:22 +0300

TrackBack

Environment:
    
Request Method: POST
Request URL: https://topxgym.pythonanywhere.com/update/8

Django Version: 4.1
Python Version: 3.9.5
Installed Applications:
['django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'members',
 'authenticate']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']
   
Traceback (most recent call last):
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/db/backends/utils.py", line 89, in _execute
    return self.cursor.execute(sql, params)
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/db/backends/sqlite3/base.py", line 357, in execute
    return Database.Cursor.execute(self, query, params)

The above exception (UNIQUE constraint failed: members_activemember.member_id) was the direct cause of the following exception:
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/contrib/auth/decorators.py", line 23, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "/home/topxgym/.virtualenvs/env/topxgym/members/views.py", line 196, in update
    form.save()
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/forms/models.py", line 548, in save
    self.instance.save()
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/db/models/base.py", line 831, in save
    self.save_base(
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/db/models/base.py", line 882, in save_base
    updated = self._save_table(
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/db/models/base.py", line 995, in _save_table
    updated = self._do_update(
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/db/models/base.py", line 1059, in _do_update
    return filtered._update(values) > 0
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/db/models/query.py", line 1215, in _update
    return query.get_compiler(self.db).execute_sql(CURSOR)
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1819, in execute_sql
    cursor = super().execute_sql(result_type)
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1395, in execute_sql
    cursor.execute(sql, params)
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/db/backends/utils.py", line 103, in execute
    return super().execute(sql, params)
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/db/backends/utils.py", line 67, in execute
    return self._execute_with_wrappers(
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/db/backends/utils.py", line 80, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/db/backends/utils.py", line 89, in _execute
    return self.cursor.execute(sql, params)
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/db/utils.py", line 91, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/db/backends/utils.py", line 89, in _execute
    return self.cursor.execute(sql, params)
  File "/home/topxgym/.virtualenvs/env/lib/python3.9/site-packages/django/db/backends/sqlite3/base.py", line 357, in execute
    return Database.Cursor.execute(self, query, params)

Exception Type: IntegrityError at /update/8
Exception Value: UNIQUE constraint failed: members_activemember.member_id

These are my two models Member and ActiveMember. It uses a OneToOneField because a member can only have one membership, activation date, end date and status. But I suspect the Unique error is coming because of the OneToOneField because the update is mixing the ID’s maybe? and A member is being assigned multiple memberships? That’s my best guess.
models.py

class Member(models.Model):
    full_name = models.CharField(max_length=125, unique=True)    
    email = models.EmailField(max_length=125, blank=True, null=True)
    phone = models.CharField(max_length=20)
    detail = models.CharField(max_length=256, blank=True, null=True)
    image = models.ImageField(max_length= 256, upload_to='media', null=True, blank=True)
    date_created = models.DateTimeField(default=django.utils.timezone.now)

    class Meta:
        verbose_name_plural = "All Members"

    def __str__(self):
        return str(f"{self.full_name}")
    
    def save(self, *args, **kwargs):
        # delete old file when replacing by updating the file
        try:
            this = Member.objects.get(id=self.id)
            if this.image != self.image:
                this.image.delete(save=False)
        except: pass # when new photo then we do nothing, normal case          
        super(Member, self).save(*args, **kwargs)


class ActiveMember(models.Model):
    member = models.OneToOneField(Member, on_delete=models.CASCADE, related_name='is_member')
    start_date = models.DateField(default=django.utils.timezone.now)
    end_date = models.DateField(default=django.utils.timezone.now)
    status = models.CharField(max_length=2, choices=(('1','Active'), ('2','Inactive')), default = '1', blank=True, null=True)
    
    def __str__(self): 
        return str(f"{self.member}")

According the the error message. The problem is happening after the form.save() is being called. The weird part the code works perfectly until you keep adding new members and updating/deleting then after a while it breaks. The update function isn’t passing the ID correctly I suspect. I might be getting ActiveMember ID not Member ID? or that doesn’t matter. Is Django smart enough to link the two?
views.py update method

@login_required(login_url='authenticate/admin_login')
def update(request, id):
    member = ActiveMember.objects.get(pk=id)
    if request.method == 'POST':
        form = ActiveMemberForm(request.POST, instance=member)
        if form.is_valid():
            # save form data to variables
            member = form.cleaned_data['member']
            start_date = form.cleaned_data['start_date']
            status = form.cleaned_data['status']

            # send WhatsApp message to inform user his membership has been activated.
            if  member.phone is not None and status == '1':
                msg = WhatsApp(member.full_name, member.phone, start_date)
                msg.send_message('customer_active')

            # save form to database.   
            form.save()
            # redirect to the success message
            return render(request, 'members/update.html', {
                'form': form,
                'success': True,
            })
    else:
        form = ActiveMemberForm(instance=member)
    return render(request, 'members/update.html', {
        'form': form, 
        'member': member,
        })

as requested the forms.py file

class MemberForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(MemberForm, self).__init__(*args, **kwargs)
        self.fields['image'].required = False
        self.fields['email'].required = False
        self.fields['date_created'].disabled = True

    class Meta:
        model = Member
        fields = (
            'full_name',
            'email',
            'phone',
            'image',
            'detail',
            'date_created',
            )


class ActiveMemberForm(ModelForm):

    def __init__(self, *args, **kwargs):
        super(ActiveMemberForm, self).__init__(*args, **kwargs)
        pass

    class Meta:
        model = ActiveMember
        fields = (
            'member',
            'start_date',
            'end_date',
            'status',
        )
        widgets = {
            'start_date': widgets.DateInput(attrs={'type': 'date'}),
            'end_date': widgets.DateInput(attrs={'type': 'date'}),
        }


    def clean(self):
        start_date = self.cleaned_data['start_date']
        end_date = self.cleaned_data['end_date']
        if start_date < timezone.now().date():
            raise ValidationError('Please enter a valid start date!')
        if end_date < timezone.now().date() or end_date < start_date:
            raise ValidationError('Please enter a valid end date!')
        return self.cleaned_data

Management/Commands/active.py script

class Command(BaseCommand):
    help = 'Deactivate expired memberships!'

    def handle(self, *args, **options):
        # Activate membership based on start_date
        ActiveMember.objects.filter(start_date=datetime.now().date()).update(status='1')
        # Deactivate membership based on end_date
        ActiveMember.objects.filter(end_date=datetime.now().date()).update(status='2')
        
        # Send WhatsApp message to active members
        active_members = ActiveMember.objects.filter(start_date=datetime.now().date())
        send_membership_status(active_members, 'customer_active')
        
        # Send WhatsApp message to expired members
        expired_members = ActiveMember.objects.filter(end_date=datetime.now().date())
        send_membership_status(expired_members, 'customer_expire')
        
       
  
def send_membership_status(memberships, template_name):
    for membership in memberships:
        name = membership.member.full_name
        phone = membership.member.phone
        if template_name == 'customer_expire':
            date = membership.end_date
        else: 
            date = membership.start_date
        if phone is not None:
            msg = WhatsApp(name, phone, date)
            msg.send_message(template_name)
    
Asked By: wormy505

||

Answers:

At the beginning of the update view function you are assigning the local member variable an ActiveMember instance:

member = ActiveMember.objects.get(pk=id)

You then pass member into the form constructor. Later in the code you assign ActiveMember.member to the member variable, which itself is a Member instance:

member = form.cleaned_data['member']

Just a shot in the dark but ids could be twisted there. Still you should always sanitize SomeModel.objects.get(pk) with a try except block like shown in the documentation. This will protect you from errors.

Answered By: coderiot