Show attribute of foreign key in add_form in Django - django

Is it possible show an attribute of foreign key in add_form django?
For example:
#models.py
class Test(models.Model):
name = models.CharField(max_length=60, db_column='Name') # Field name made lowercase.
product=models.ForeignKey(Product, on_delete=models.DO_NOTHING)
class Product(models.Model):
id = models.AutoField(db_column='Id', primary_key=True)
description = models.CharField(max_length=60, db_column='Description')
#admin.py
class TestAdmin(admin.ModelAdmin):
fields = ['name','product','get_description']
readonly_fields = ['get_description']
def get_description(self):
return self.product.description
Naturally this code raise exception the ErrorFields in 'get_description'.
But, does exist a way to show the 'get_description' when I insert an entry of model Test?
Thanks

You can override the ModelAdmin.get_form and alter the form that it uses, to add a field of your choosing, filled with whatever you like. For example:
#admin.py
class TestAdmin(admin.ModelAdmin):
fields = ['name','product']
def get_form(self, request, obj=None, **kwargs):
if obj.product:
# self.form.base_fields is an OrderedDict(field_name=form.Field, ...)
self.form.base_fields['product_description'] = forms.CharField(required=False, initial=obj.product.description)
self.form.base_fields['product_description'].widget.attrs['readonly'] = True
return super(TestAdmin, self).get_form(request, obj, **kwargs)
That will add a readonly text input field as the last field in your "add" form. If you wanted it placed in a different spot witin the form, you'd have to work out some logic to rebuild self.form.base_fields as a new OrderedDict in exactly the order you'd like.

Related

Explicitly defining fields on ModelSerializer overrides Django's Model parameters like verbose_name and validators

Imagine having a simple model like the one bellow:
from utils.validators import name_validator
class Customer(models.Model):
name = models.CharField(verbose_name="Customer Name", validators=[name_validator])
email = models.EmailField(verbose_name="Customer Email")
def __str__(self):
return self.name
Now if I explicitly define a filed on my serializer, both validators and verbose_name are lost. I can use label= and validatos= when defining the field on my serializer but I don't want to repeat myself. What if I have multiple serializer pointing to the same Model?
class CustomerSerilizer(serializers.ModelSerializer):
custom_field_name = serializers.CharField(source="name")
class Meta:
model = Customer
fields = "__all__"
Is there anyway to prevent this from happening?
I'm not sure if it's the perfect way of doing this or not, but I managed to achieve my desired behavior by writing a custom ModelSerializer which sets label and validators if they are not being passed when explicitly defining a field on the serializer.
class CustomModelSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super(CustomModelSerializer, self).__init__(*args, **kwargs)
model = self.Meta.model
model_fields = [f.name for f in model._meta.get_fields()]
for field_name, field_instance in self.fields.items():
source_field = field_instance.source
if source_field in model_fields:
model_field = model._meta.get_field(source_field)
if "label" not in field_instance._kwargs:
field_instance.label = model_field.verbose_name
if "validators" not in field_instance._kwargs:
field_instance.validators.extend(model_field.validators)

Field is required on foreign key error Django rest Framework

I am in a issue in which my api is creating a duplicate data as I am just passing ingredient name and its restaurant not Pk etc. So to prevent this thing I made a
class Meta:
unique_together = ('restaurant' ,'name')
constraint in my model . Before this everything was fine just duplicate entries were creating. Now after adding this constraint its saying 'Restaurant field is required' and my serializer is not valid.
My Ingredient model is like this
class Ingredient(models.Model):
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE )
name = models.CharField(max_length=255 ,)
class Meta:
unique_together = ('restaurant' ,'name')
def __str__(self):
return str(self.name)
and my Ingredient Serializer is like
class IngredientsSerializer(serializers.ModelSerializer):
restaurant = RestaurantSerializer(required=False)
class Meta:
model = Ingredient
fields = '__all__'
def create(self, validated_data):
restaurant = validated_data.get('restaurant')
name = validated_data.get('name', None)
ingredient = Ingredient.objects.create(restaurant=restaurant, name=name)
return ingredient
And my view.py for serialize is like
#permission_classes([AllowAny])
class CreateIngredients(APIView):
def post(self, request):
serializer = IngredientsSerializer(data=request.data)
if serializer.is_valid():
restaurant=Restaurant.objects.get(id=request.POST['restaurant'])
obj_article = serializer.save(restaurant=restaurant)
return Response(success_response(data='none', msg='Ingredient added'), status=status.HTTP_200_OK)
Looks like you are not sending restaurant in your request.
Even though in your serializer you've defined that restaurant isn't required.
restaurant = RestaurantSerializer(required=False)
What makes it invalid is the create method. In there you have
ingredient = Ingredient.objects.create(restaurant=restaurant, name=name)
which uses objects create method (your restaurant argument here is probably None) and since you've defined unique together with restaurant and name this means neither of them can be None.

Django Use Many To Many with Through in Form

I have a data model where I am using a manual intermediate table for a m2m relationship.
Building on the classical example from the django doc:
from django.db import models
INSTRUMENT_CHOICES = (
('guitar', 'Guitar'),
('bass', 'Bass Guitar'),
('drum', 'Drum'),
('keyboard', 'Keyboard'),
)
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Leadership')
def __str__(self):
return self.name
def get_leadership():
return self.leadership_set.first()
class Leadership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
instrument = models.CharField('Playing Instrument', choices=INSTRUMENT_CHOICES,
max_length=15,
null=True,
blank=False)
class Meta:
unique_together = ('person', 'group')
When I create a new group I also want to specify who is going to be the leader, and for this relationship also specify which instrument he will play in that group.
What really confuses me, given also the lack of documentation on this topic is how to handle this kind of relationship in forms.
This is the form I came with:
class InstrumentField(forms.ChoiceField):
def __init__(self, *args, **kwargs):
super().__init__(INSTRUMENT_CHOICES, *args, **kwargs)
class GroupForm(forms.ModelForm):
instrument = InstrumentField(required=False)
class Meta:
model = Group
fields = ['name',
'members'
'instrument'] # This works but it's not correctly initalized in case of edit form
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk is not None: # editing
# PROBLEM: this doesn't work
self.fields["instrument"].initial = self.instance.get_leadership().instrument
def save(self, commit=True):
group = super().save(commit=False)
if commit:
group.save()
if 'instrument' in self.changed_data:
leader = self.cleaned_data.get('members').first()
instrument = self.cleaned_data['instrument']
Leadership.objects.update_or_create(person=leader, group=group, defaults={'instrument': instrument})
return group
As suggested in the django doc I am manually instantiating Leadership objects (see the form save method).
What I couldn't solve is how to populate the instrument field in case of form editing. I try to do this in the __init__: first I check that we are in "edit" mode (the instance has a pk) then I get the relevant Leadership object (see Group.get_leadership) and from that I extract the instrument and I assign it to the fields["instrument"].initial.
This doesn't work.
I could inspect that the initial value was set but then when I render the form the default choice value is shown (the first value of the INSTRUMENT_CHOICES).
What am I missing here?
Is there a better way or a better docs on how to handle m2m with through model in forms?

add foreign key fields to form

I'm trying to create a form from this model:
class A(models.Model):
u = models.OneToOneField(User)
and then create this form:
class AForm(ModelForm):
class Meta:
model = A
fields = ['u']
then i create an instance of that form in my view and send it to my template as a context I'll get a drop down list to choose from existing users but what i want to do is to have a text field to change my current user's first name or last name.
I'll be grateful if you could help me to change my form class to get the right result.
Thank you
You can add the first and last name fields to the AForm ModelForm in the following way:
class AForm(ModelForm):
first_name = forms.CharField(max_length=30, blank=True)
last_name = forms.CharField(max_length=30, blank=True)
class Meta:
Model = A
def __init__(self, *args, **kwargs):
super(AForm, self).__init__(*args, **kwargs)
self.fields['first_name'].initial = self.instance.u.first_name
self.fields['last_name'].initial = self.instance.u.last_name
def save(self, commit=True):
self.instance.u.first_name = self.cleaned_data['first_name']
self.instance.u.last_name = self.cleaned_data['last_name']
self.instance.u.save()
return super(AForm, self).save(commit=commit)
In this case you do not need a modelform of A but a modelform of User. You would need to set the form's instance appropriately in the view. For example,
a_record = A.objects.get_object_or_404(A, id=1)
form = self.UserForm(instance=a.u) # UserForm is a modelform of User

ModelForms and ForeignKeys

I got the following models:
class Project(models.Model):
name = models.CharField(max_length=50)
class ProjectParticipation(models.Model):
user = models.ForeignKey(User)
project = models.ForeignKey(Project)
class Receipt(models.Model):
project_participation = models.ForeignKey(ProjectParticipation)
Furthermore I have the following CreateView:
class ReceiptCreateView(LoginRequiredMixin, CreateView):
form_class = ReceiptForm
model = Receipt
action = 'created'
I now want a dropdown menu where the User can choose the project, the new receipt should be for. The user should only see the project he is assigned to.
How can I do that?
The simply answer is just create a model form read the docs, this is fundamental.
You may also want to look at related names, that way you can get the reverse on the FK.
class ProjectParticipation(models.Model):
user = models.ForeignKey(User)
project = models.ForeignKey(Project, related_name='ProjectParticipation')
I found a solution using a ModelChoiceField:
class ProjectModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.project
class ReceiptForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ReceiptForm, self).__init__(*args, **kwargs)
self.fields['project_participation'] = ProjectModelChoiceField(queryset= ProjectParticipation.objects)
class Meta:
model = Receipt
And then in the CreateView:
class ReceiptCreateView(...)
def get_form(self, form_class):
form = super(ReceiptCreateView, self).get_form(form_class)
form.fields['project_participation'].queryset = ProjectParticipation.objects.filter(user=self.request.user)
return form
Is there a solution to filter the query set directly in the ModelForm?