Django - If one model field exists, another field must be empty/inaccessible - django

I have a model called Team. Team has one User as team_leader and can have multiple team_member's.
Under User, I added the following fields:
class User(AbstractUser):
team_leader = models.OneToOneField(Team, null=True, blank=True)
team_member = models.ForeignKey(Team, null=True, blank=True)
Obviously a User shouldn't be a team_leader and team_member at the same time. I have set both fields as null/blank so I can always just leave one of them blank. However for extra precaution I'd like to completely disable one of these fields when the other field is activated.
I'm aware of the disabled function, but it is only for disabling the field on a form. I want to eliminate any chance of tampering by completely disabling one model field.
Is there a way to do this?

class UserForm(ModelForm):
def clean(self):
cleaned_data = super(UserForm, self).clean()
team_leader = cleaned_data.get('team_leader', None)
team_member = cleaned_data.get('team_member', None)
if team_leader and len(team_member)!=0:
if team_leader in team_member:
raise forms.ValidationError("team_leader can't in team_member")
class Meta:
model = User
fields = ['team_leader', 'team_member']
class UserAdmin(admin.ModelAdmin):
form = UserForm

Related

How to see objects of manytomany field by related_name in admin page?

I have these two models:
class User(AbstractUser):
is_teacher = models.BooleanField(default=False, null=False)
class Course(models.Model):
teacher = models.ForeignKey(User, on_delete=models.CASCADE, related_name='teacher_courses')
students = models.ManyToManyField(User, blank=True, related_name='student_courses')
Course model has a ManyToMany field and a ForeignKey to User model. In django's admin page, you are able to see a course's student/teacher. Is there a way to make it as you can have a list of a user's courses in admin page to see/add/remove courses for a user?
You can define a callable on your ModelAdmin class and add it to list_display. To make the courses editable on an user's page use sub classes of InlineModelAdmin.
class TeacherCourseInlineAdmin(admin.TabularInline):
model = Course
fk_name = "teacher"
class StudentCourseInlineAdmin(admin.TabularInline):
model = Course
fk_name = "student"
class UserAdmin(admin.ModelAdmin):
list_display = ("username", "teacher_courses")
inlines = [TeacherCourseInlineAdmin, StudentCourseInlineAdmin]
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).prefetch_related("teacher_courses")
#admin.display(description='Courses')
def teacher_courses(self, user):
return [c.name for c in user.teacher_courses.all()]
Note that it makes sense to override ModelAdmin.get_queryset() to add a call to prefetch_related() so that Django fetches all related courses in one extra query instead of performing one additional query for every user object.

Making form fields - read only or disabled in DJANGO updateView

I have a model which I will update using an updateView generic class based function. How can I make specific fields as read only ?
example :
Models.py:
class Employee(models.Model):
emp_no = models.IntegerField( primary_key=True)
birth_date = models.DateField()
first_name = models.CharField(max_length=14)
last_name = models.CharField(max_length=16)
gender = models.CharField(max_length=1)
hire_date = models.DateField()
class Meta:
verbose_name = ('employee')
verbose_name_plural = ('employees')
# db_table = 'employees'
def __str__(self):
return "{} {}".format(self.first_name, self.last_name)
views.py :
class EmployeeUpdate(UpdateView):
model = Employee
fields = '__all__'
How can I make the first_name readonly inside a UpdateView ?
Note: I want to have the model(charfield) the same, But it should be read only inide an UpdateView.
When one wants to customize their forms the easiest way is to make a form class. A generic view is not really meant to provide all features a form class does, even though it makes life a little easy by generating the form for you by itself.
You want to be using a ModelForm [Django docs] and set disabled=True [Django docs] on your field:
from django import forms
class EmployeeUpdateForm(forms.ModelForm):
first_name = forms.CharField(max_length=14, disabled=True)
class Meta:
model = Employee
fields = '__all__'
Note: The disabled boolean argument, when set to True, disables a form field using the disabled HTML attribute so that it won’t be
editable by users. Even if a user tampers with the field’s value
submitted to the server, it will be ignored in favor of the value from
the form’s initial data.
Now in your view you simply want to set the form_class attribute:
class EmployeeUpdate(UpdateView):
model = Employee
form_class = EmployeeUpdateForm

Django multi-form validation from different models

I have been struggling to normalize a few tables/models I have in Django that are to be used in one large form.
Let's say I have 2 forms based off of 2 models, 1 of which is dependant of the other. Is there a way to add form validations in 1 model when a specific selection is made in a different model?
The cleaned_data from 1 form isn't available in the other form, is the primary issue. I have instantiated both forms in the same view though.
So basically, in 1 form I need to reference fields which are in a different model (a different modelForm that's instantiated on the same page)
Form2 based off table/model2 :
def clean(self):
cleaned_data = super().clean()
if 'model1_field' in cleaned_data and not cleaned_data['model2_field']:
self.add_error('model2_field', forms.ValidationError('This field is required.'))
else:
print('No validations are needed')
More details on the dependency (trying to follow concepts of db normalization), and there are a bunch of fields that are required in model2, only when 'selection2'or 'is_option1' from model1 is selected.
--- Models------
MULTI_OPTIONS = (
('selection1', 'selection1'),
('selection2', 'selection2') # Display and make model2 required
)
class Model1(models.Model):
primary_key = models.CharField(db_column='primary_table1_key', max_length=10) # Maps tocommon key for all related tables
is_selected = models.BooleanField(db_column='IsSelected', blank=True, default=None)
multi_select_options = MultiSelectField(db_column='SelectedOptions', max_length=150, choices = MULTI_OPTIONS, blank=True, null=True)
is_option1 = models.BooleanField(db_column='IsOption1', blank=True, null=True, default=None) # No validations needed for Model1SubOptions
is_option2 = models.BooleanField(db_column='IsOption2', blank=True, null=True, default=None)# If option2 or 'selection2' is made in Model1, then make required sub_selections and all other fields in Model1SubOptions
class Meta:
managed = True
# This should become required if Model1 specific option (from a multiselect (Or) a specific boolean field is set to true)
class Model2(models.Model):
primary_key = models.CharField(db_column='primary_table1_key', max_length=10) # Maps tocommon key for all related tables
sub_selections = MultiSelectField(db_column='SubOptions', max_length=150, choices = (('Some', 'Some'), ('Other','Other')), blank=True, null=True)
other_field2 = models.PositiveIntegerField(db_column='OtherField2', blank=True, null=True)
---------- Forms ---------
class Model1Form(forms.ModelForm):
class Meta:
model = models.Model1
fields = ( 'is_selected', 'multi_select_options', 'is_option1', 'is_option2')
def clean(self):
cleaned_data = super().clean()
if('multi_select_options' in cleaned_data):
multi_select_options = cleaned_data['multi_select_options']
if(not multi_select_options):
self.add_error('multi_select_options', forms.ValidationError('This field is required'))
# if(('selection1' in multi_select_options) and check_data_does_not_exist(cleaned_data, 'model2')):
# Validate model2
class Model2Form(forms.ModelForm):
class Meta:
model = models.Model2
fields = ( 'sub_selections', 'other_field2')
def clean(self):
cleaned_data = super().clean()
# if(('selection1' in multi_select_options) and check_data_does_not_exist(cleaned_data, 'multi_select_options')):
# Validate model2
You can create a form that has all the fields from both models, you can then do all validation in one clean method and then your view can handle saving he individual models
class CombinedForm(forms.Form):
# All fields from Model1
# All fields from Model2
def clean(self):
# Do all validation for fields that are dependent on each other
class CombinedView(FormView):
form_class = CombinedForm
def form_valid(self, form):
Model1.objects.create(
param1=form.cleaned_data['param1'],
param2=form.cleaned_data['param2'],
)
Model2.objects.create(
param3=form.cleaned_data['param3'],
param4=form.cleaned_data['param4'],
)

Skip one or more constraints in Django form.is_valid()

Given the following model:
class User(models.Model):
role = models.IntegerField(default=0, blank=True)
name = models.CharField(max_length=255, blank=False, null=False)
email = models.EmailField(max_length=128, unique=True, blank=False, null=False)
I need that form.is_valid() would skip the unique constraint on email field.
It's essential, that emails would be unique, however in one particular view I wanna use get_or_create, which doesn't seem to work:
if form.is_valid():
usr, usr_created = models.User.objects.get_or_create(email=form.email)
<...>
Is this possible?
This is not possible.
unique=True creates a database constraint that checks for the uniqueness of the field. By its very design this disallows non-unique values, and the only way to circumvent this is to remove the constraint.
If the problem is that you want to allow multiple empty values, you can set null=True. Multiple NULL values are still considered unique by the constraint.
Don't list the field you want to override in the fields list and create a new custom field in the ModelForm:
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ["role", "name"]
email = forms.EmailField(max_length=128)
def save(self, commit=True):
instance = super(PatientForm, self).save(commit=False)
instance.email= self.cleaned_data.get("email")
return instance.save(commit)

Django rest framework : extend user model for customer - one to one field

I have a customer model in Bcustomer app that extends the django User model, So I will save the basic details such as name in User table and the remaining data (city, etc) in customer table.
When I call the below code through API, it shows the following error. But data is saving in the tables. I also want to implement the get and put calls for this api.
Got AttributeError when attempting to get a value for field `city` on serializer `CustomerSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'city'.
my Bcustomer/models.py
class BCustomer(models.Model):
customer = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True, blank=True )
address = models.CharField(max_length=50)
city = models.CharField(max_length=256)
state = models.CharField(max_length=50)
user = models.ForeignKey(settings.AUTH_USER_MODEL, db_index=True, on_delete=models.CASCADE, related_name='customer_creator')
# more fields to go
def __str__(self):
# return str(self.name) (This should print first and last name in User model)
class Meta:
app_label = 'bcustomer'
my Bcustomer/serializers.py
from django.contrib.auth import get_user_model
from models import BCustomer
class CustomerSerializer(serializers.HyperlinkedModelSerializer):
city = serializers.CharField()
class Meta:
model = get_user_model()
fields = ('first_name', 'email','city')
def create(self, validated_data):
userModel = get_user_model()
email = validated_data.pop('email', None)
first_name = validated_data.pop('first_name', None)
city = validated_data.pop('city', None)
request = self.context.get('request')
creator = request.user
user = userModel.objects.create(
first_name=first_name,
email=email,
# etc ...
)
customer = BCustomer.objects.create(
customer=user,
city=city,
user=creator
# etc ...
)
return user
my Bcustomer/views.py
class CustomerViewSet(viewsets.ModelViewSet):
customer_photo_thumb = BCustomer.get_thumbnail_url
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = BCustomer.objects.all()
serializer_class = CustomerSerializer
my Bcustomer/urls.py
router.register(r'customer', views.CustomerViewSet, 'customers')
POST request format
{
"first_name":"Jsanefvf dss",
"city":"My City",
"email":"myemail#gmail.com",
#more fields
}
I also need to implement put and get for this api. Now data is saving in both tables but shows the error.
Sure it complains.
Validation goes well, so does the creation but once it's created, the view will deserialize the result and return it to the client.
This is the point where it goes south. Serializer is configured to think that city is a field of the default user while it actually is part of BCustomer. In order to work this around, you should set the source argument to the city field. You might need to update the serializer's create/update to reflect that change, not sure about that one.