Home > OS >  Set and not let modify foreign key value (user)
Set and not let modify foreign key value (user)

Time:10-17

I have 2 models that I want to preset the value and ideally have it hidden from the Django admin when creating new record, this way the user don't amend this value. This are the created and modified by that are foreign keys to users.

I found this link https://pypi.org/project/django-currentuser/, that i thought it might do the job, however it reverted my django from the latest version to version 3, so I dont want to use it, and also, it doesnt work if i set either created or last modified but not both, if i set it in the 2 models i get 4 errors.

I am wondering if there is an easy way to set this default value?

from django.db import models
from email.policy import default
from django.contrib.auth.models import User
from django.utils import timezone
# from django.contrib import admin
# https://pypi.org/project/django-currentuser/
from django_currentuser.middleware import (get_current_user, get_current_authenticated_user)
from django_currentuser.db.models import CurrentUserField

class Company(models.Model):
    modified_by = models.ForeignKey(User, related_name='company_modified_by', unique = False, on_delete=models.CASCADE)
    created_by = CurrentUserField()
    created_date = models.DateTimeField(auto_now_add=True)
    modified_date = models.DateTimeField(auto_now_add=True)
    name = models.CharField(max_length=150, unique = True)
    class Meta:
        verbose_name = "Company"
        verbose_name_plural = "Companies"        
    def __str__(self):
        return self.name   

class UserProfile(models.Model):
    modified_by = models.ForeignKey(User, related_name='user_profile_modified_by', unique = False, on_delete=models.CASCADE)#CurrentUserField(on_update=True)
    created_by = CurrentUserField()
    created_date = models.DateTimeField(auto_now_add=True)
    modified_date = models.DateTimeField(auto_now_add=True)

CodePudding user response:

You can use editable=False, eg,

modified_by = models.ForeignKey(User, related_name='company_modified_by', unique = False, on_delete=models.CASCADE, editable=False)

According to the docs, If False, the field will not be displayed in the admin or any other ModelForm. They are also skipped during model validation. Default is True.

That way, you can set it programmatically during creation (eg, via a request to a view) and not have to worry about it being edited.

def create_view(request):
    if request.method == "POST":
        company_form = CompanyForm(request.POST)
        company_form.instance.created_by = request.user
        company_form.save()

(Also - don't forget, use

modified_date = models.DateTimeField(auto_now=True)

for modified dates. auto_now_add is for one time creation updates.)

CodePudding user response:

I've learned that instance doesn't work as described in a previous stackoverflow interaction. I've done some tinkering figured out how to do my usual is_edit flag in the admin

This is what I've come up with. It requires changing the admin.py and adding a new form. The values will still show up in that table in the admin page, which I assume is good, they're just hidden in the new edit forms.

  • Note: I only did Company as I'm not 100% sure on how UserProfile works as the only two fields are supposed to be hidden ones, so what's to edit?

admin.py

from django.contrib import admin

# this \/ needs to change
from myapp.forms import CompanyForm
from myapp.models import Company

class CompanyAdmin(admin.ModelAdmin):
    # columns to show in admin table
    list_display = (
        'name',
        'created_by', 'created_date',
        'modified_by', 'modified_date',
        )

    # custom form
    form = CompanyAdminForm

    # override default form_save to pass in the request object
    #   - need request.user inside the save method for `{x}_by = user`
    def save_form(self, request, form, change):
        return form.save(commit=False, request=request)


admin.site.register(Company, CompanyAdmin)

forms.py

from django import forms
from django.contrib import admin

# this \/ needs to change
from myapp.models import Company

class CompanyForm(forms.ModelForm):
    class Meta:
        model = Company
        fields =['name']
        exclude =['modified_by', 'created_by', 'created_date', 'modified_date']

    def __init__(self,  *args, **kwargs):

        # We must determine if edit here, as 'instance' will always exist in the save method
        self.is_edit = kwargs.get('instance') != None

        super(CompanyForm, self).__init__(*args, **kwargs)

    def save(self, commit=True, *args, **kwargs):

        # We must Pop Request out here, as super() doesn't like extra kwargs / will crash
        self.request =  kwargs.pop('request') if 'request' in kwargs else None

        obj = super(CompanyForm, self).save(commit=False, *args, **kwargs)

        # do your stuff!
        from datetime import datetime
        if self.is_edit:
            obj.modified_date = datetime.now().date()
            obj.modified_by = self.request.user
        else:
            obj.created_date = datetime.now().date()
            obj.created_by = self.request.user

        if commit:
            obj.save()
        return obj

Note: But you can reuse the Company form for a non-admin form, you just have to remember to call the save like: form.save(commit=False, request=request)

# Example (Might need some minor tinkering)
def myview(request):
    if request.method == 'POST':
        form = CompanyForm(request.POST)
        if form.is_valid():
            form.save(commit=True, request=request)

Normally I inject vars into the declaration __init__, ex form = CompanyForm(request.POST, request=request, is_edit=True) instead of the save() but 1 look at contrib/admin/options.py ModelAdmin.get_form() & no thanks!

  • Related