My goal is to loop through all form fields and to assign certain classes to them like this:
class ContactForm(forms.Form):
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
for field_name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control input-sm plain'
if field.required == True:
field.widget.attrs['required'] = ''
class Meta:
model = Contact
fields = '__all__'
The issue with this code is that self.fields.items() seems to be empty (and as a result I never get into the for-loop).
My guess is that the issue arose either because of my upgrade from Django 1.9 and python 2 to Django 1.10 and python 3, or because of custom manager present in the definition of the underlying model.
Could anyone share expertise on this?
class Contact(BaseMixin, DeleteMixin):
provider_account = models.ForeignKey(ProviderAccount, models.DO_NOTHING)
client_id = models.IntegerField()
name = models.CharField(max_length=300)
profile_photo_url = models.CharField(max_length=100, default = 'no_image.jpg')
event_type_id = models.IntegerField(EventType.choices(), blank=True, null=True)
is_satisfied = models.NullBooleanField()
objects = CustomQuerySetManager()
class Meta:
managed = False
db_table = 'contact'
class QuerySet(QuerySet):
#....
Your form is a standard form, not a model form; the Meta class is ignored and the only fields are those you define yourself.
Your form should inherit from forms.ModelForm for this to work.
Related
i'm working on a django project and i got this error (Cannot assign "'11'": "Product.category" must be a "CategoryProduct" instance.) anyone here can help me please.
Model:
class Product(models.Model):
name = models.CharField("Nombre", max_length=150)
category = models.ForeignKey(CategoryProduct, on_delete=models.SET_NULL, null=True, related_name='category')
def __str__(self):
return self.name
View:
class ProductCreateView(CreateView):
model = Product
form_class = ProductForm
success_url = '/adminpanel/products/'
def post(self, request, *args, **kwargs):
form = self.get_form()
category = CategoryProduct.objects.get(id=request.POST['category'])
if form.is_valid():
product = form.save(commit=False)
product.category = category
product.save()
Form:
class ProductForm(forms.ModelForm):
name = forms.CharField(max_length=150, label="Nombre")
category = forms.ChoiceField(choices=[(obj.id, obj.name) for obj in CategoryProduct.objects.all()], label="Categoría")
class Meta:
model = Product
fields = ['name', 'category']
You can let Django's ModelForm do its work, this will create a ModelChoiceField [Django-doc], which is where the system gets stuck: it tries to assign the primary key to category, but that should be a ProductCategory object, so you can let Django handle this with:
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ['name', 'category']
If you want to specify a different label, you can use the verbose_name=… [Django-doc] from the model field, or specify this in the labels options [Django-doc] of the Meta of the ProductForm. So you can specify Categoria with:
class Product(models.Model):
name = models.CharField('Nombre', max_length=150)
category = models.ForeignKey(
CategoryProduct,
on_delete=models.SET_NULL,
null=True,
related_name='products',
verbose_name='Categoria'
)
def __str__(self):
return self.name
then the CreateView can just use its boilerplate logic:
class ProductCreateView(CreateView):
model = Product
form_class = ProductForm
success_url = '/adminpanel/products/'
Note: The related_name=… parameter [Django-doc]
is the name of the relation in reverse, so from the Category model to the Product
model in this case. Therefore it (often) makes not much sense to name it the
same as the forward relation. You thus might want to consider renaming the category relation to products.
My form triggers form_invalid when the field "category" is empty.
The weird thing is, when the view displays, the "description" field does not have the asterisk indicating it's required, unlike "name" or "enabled", for instance. Also, when I try to send the form with an empty name, it correctly displays a little yellow mark and says "This field is required", but it doesn't say that when the category is empty.
So, it seems to correctly recognize that the category is not required, it just says it's invalid after I send the form.
My form looks like this:
class ProductForm(forms.Form):
name = forms.CharField(max_length=80, required=True)
category = forms.ModelChoiceField(queryset=None, required=False, label='Categoría')
description = forms.CharField(max_length=150, required=False)
price = forms.FloatField(required=True)
image = forms.ImageField(allow_empty_file=True, required=False)
extras = forms.FileField(allow_empty_file=True, required=False)
enabled = forms.BooleanField(required=False, initial=True)
def __init__(self, user, *args, **kwargs):
self.user = user
super(ProductForm, self).__init__(*args, **kwargs)
self.fields['name'].label = 'Nombre'
self.fields['description'].label = 'Descripción'
self.fields['price'].label = 'Precio'
self.fields['image'].label = 'Imagen'
self.fields['extras'].label = 'Extras'
categories = Category.objects.filter(store=Profile.objects.get(user=user).store)
if categories.count() == 0:
self.fields['category'].required = False
self.fields['category'].queryset = categories
self.fields['enabled'].label = 'Habilitado'
It is included to my view in this way:
class ProductCreateView(LoginRequiredMixin, CreateView):
template_name = 'products/product_form.html'
model = Product
fields = ["name", "category", "description", "price", "image", "enabled", "extra"]
success_url = reverse_lazy("orders:products")
And my model looks like this:
class Product(models.Model):
store = models.ForeignKey(Store, related_name="products", on_delete=models.PROTECT)
name = models.CharField(max_length=100, verbose_name="Nombre")
description = models.CharField(max_length=500, verbose_name="Descripción", null=True)
price = models.FloatField(verbose_name="Precio")
image = models.ImageField(upload_to="media/", verbose_name="Imagen", null=True, blank=True)
enabled = models.BooleanField(default=False, verbose_name="Habilitado")
extra = models.FileField(upload_to="media/files/", verbose_name="Extras", max_length=254, null=True, blank=True)
category = models.ForeignKey(Category, on_delete=models.PROTECT, null=True)
detail_enabled = models.BooleanField(default=False)
You never use the ModelForm you constructed. Django will create its own since nowhere you specify form_class=… [Django-doc] in your CreateView. But that will not be sufficient, since Django will not pass a user by default. You will need to override the .get_form_kwargs(…) [Django-doc] as well to pass the user.
You also should make the ProductForm a ModelForm, since otherwise it has no .save() method:
class ProductForm(forms.ModelForm):
# …
class Meta:
model = Product
fields = ['name', 'category', 'description', 'price', 'image', 'enabled', 'extra']
In your view you thus specify the form_class, and override the get_form_kwargs, to inject the user in the ModelForm constructor:
class ProductCreateView(LoginRequiredMixin, CreateView):
template_name = 'products/product_form.html'
model = Product
form_class = ProductForm
success_url = reverse_lazy('orders:products')
def get_form_kwargs(self, *args, **kwargs):
fk = super().get_form_kwargs(*args, **kwargs)
fk['user'] = self.request.user
return fk
I am trying to find out an efficient way to set a field value within form init method. My models are similar to below
class Users(models.Model):
firstname = models.CharField()
lastname = models.CharField()
class profile(models.model):
user = models.ForeignKey(Users, on_delete=models.PROTECT)
class logindetails(models.model):
user = models.ForeignKey(Users, on_delete=models.PROTECT)
profile = models.ForeignKey(profile, on_delete=models.PROTECT)
login_time = models.DateField(auto_now=True)
My form is like as below:
class LoginForm(forms.ModelForm):
class Meta:
model = logindetails
fields = [__all__]
def __init__(self, *args, **kwargs):
self._rowid = kwargs.pop('rowid', None)
super(LoginForm, self).__init__(*args, **kwargs)
instance = profile.objects.get(id=self._rowid)
self.fields['user'] = instance.user <--- Facing difficulties here
Any help will be appreciated.
Django had built-in ways of setting initial form values, the documentation is available here: https://docs.djangoproject.com/en/3.0/ref/forms/api/#dynamic-initial-values
My question is somewhat related to this one with some differences. I have a model similar to this one:
class Project(models.Model):
project_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
created_by_id = models.ForeignKey('auth.User', related_name='project', on_delete=models.SET_NULL, blank=True, null=True)
created_by = models.CharField(max_length=255, default="unknown")
created = models.DateTimeField(auto_now_add=True)
With the following serializer:
class ProjectSerializer(serializers.ModelSerializer):
created_by = serializers.ReadOnlyField(source='created_by_id.username')
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created')
And corresponding view:
class projectsView(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
def perform_create(self, serializer):
serializer.save(created_by_id=self.request.user)
This code behaves like I want but forces information redundancy and does not leverage the underlying relationnal database. I tried to use the info from the linked question to achieve a "write user id on database but return username on "get"" in a flat json without success:
Removing the "created_by" field in the model. Replacing the serializer with:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
class ProjectSerializer(serializers.ModelSerializer):
created_by = UserSerializer(read_only=True)
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), source='created_by', write_only=True)
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created_by_id', 'created')
Which would NOT 100% give me what I want, i.e. replace the user id with the username in a flat json but return something like: {'project_id': <uuid>, 'created_by': <user json object>, 'created': <data>}. But still I get a {'created_by_id': ['This field is required.']} 400 error.
Question: How can I write a user id to a database object from the request.user information to refer to an actual user id but return a simple username in the GET request on the projectsView endpoint without explicitly storing the username in the Model? Or more generally speaking, how can I serialize database objects (Django models) into customer json response by using default serialization DRF features and default DRF views mixins?
Alternate formulation of the question: How can I store an ID reference to another DB record in my model (that can be accessed without it being supplied by the payload) but deserialize a derived information from that object reference at the serializer level such as one specific field of the referenced object?
I would recommend you to use Two different serializers for Get and POST operations. Change your serializers.py as
class ProjectGetSerializer(serializers.ModelSerializer):
created_by_id = serializers.StringRelatedField()
class Meta:
model = Project
fields = '__all__'
class ProjectCreateSerializer(serializers.ModelSerializer):
created_by_id = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), default=serializers.CurrentUserDefault())
def create(self, validated_data):
return Project.objects.create(**validated_data, created_by=validated_data['created_by_id'].username)
class Meta:
model = Project
fields = '__all__'
Also, I reccomend ModelViewSet for API class if you are looking for CRUD operations. Hence the view will be like this,
class projectsView(viewsets.ModelViewSet):
queryset = Project.objects.all()
def get_serializer_class(self):
if self.action == 'create':
return ProjectCreateSerializer
return ProjectGetSerializer
So, the payload to create Project is,
{
}
One thing you should remember, while you trying to create Project user must logged-in
UPDATE - 1
serializer.py
class ProjectCreateSerializer(serializers.ModelSerializer):
created_by_id = serializers.StringRelatedField()
class Meta:
model = Project
fields = '__all__'
def create(self, validated_data):
return Project.objects.create(**validated_data, created_by_id=self.context['request'].user)
views.py
class projectsView(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectCreateSerializer
The error is in the write_only field options. The required parameter default value is set to True while the intent is to not make it required if we take a look at the model. Here in the view, I use the perform_create as post processing to save on the Model DB representation. Since required default value is True at the creation level, the first .save() to the DB fails. Since this is purely internal logic, the required is not necessary. So simply adding the required=False option on the PrimaryKeyRelatedField does the job:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
class ProjectSerializer(serializers.ModelSerializer):
created_by = UserSerializer(read_only=True)
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), source='created_by', write_only=True, required=False)
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created_by_id', 'created')
Enforcing the required=True at the Model level as well would require to override the .save function of the serializer if I insist on playing with the logic purely at the serializer level for deserialization. There might be a way to get the user ref within the serializer as well to keep the views implementation even more 'default'... This can be done by using the default value from Jerin:
class ProjectSerializer(serializers.ModelSerializer):
created_by = UserSerializer(read_only=True)
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), source='created_by',
write_only=True,
required=False,
default=serializers.CurrentUserDefault())
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created_by_id', 'created')
Now to flaten the json with username only, you need to use a slug field instead of the UserSerializer:
class ProjectSerializer(serializers.ModelSerializer):
created_by = serializers.SlugRelatedField(
queryset=User.objects.all(), slug_field="username")
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), source='created_by', write_only=True, required=False)
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created_by_id', 'created')
And then only the username field value of the User Model will show at the create_by json tag on the get payload.
UPDATE - 1
After some more tweaking here is the final version I came up with:
class ProjectSerializer(serializers.ModelSerializer):
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), write_only=True, required=False, default=serializers.CurrentUserDefault())
created_by = serializers.SerializerMethodField('creator')
def creator(self, obj):
return obj.created_by_id.username
class Meta:
model = Project
fields = ('project_id', 'created_by_id', 'created_by', 'created')
i try to order properties in the admin but it does not work
model
class Physic(models.Model):
name = models.ForeignKey(Property, verbose_name=_('name'), null=True, blank=True,)
lapropriete = models.CharField(_('property'), max_length=100)....
class Essai_Temperature(models.Model):
name = models.ForeignKey(Material,
nature_unit = models.ForeignKey(Property,
choix = ChainedForeignKey(Physic, verbose_name=_('properties'), null....
form
class Essai_TemperatureForm(forms.ModelForm):
class Meta:
model = Essai_Temperature
def __init__(self, *args, **kwargs):
super(Essai_TemperatureForm, self).__init__(*args, **kwargs)
self.fields['choix'].queryset = Physic.objects.order_by('-lapropriete')
admin
class Essai_TemperatureInline(admin.TabularInline):
model = Essai_Temperature
form = Essai_TemperatureForm
extra = 5
what is the problem here ?
maybe choix ?
I have dropdown list ('Hardness Vickers (GPa)', Shear Modulus (MPa)'......
Why not just use the ordering attribute of the ModelAdmin (which will also work on the TabularInline - it previously didn't work but has been fixed)
class Essai_TemperatureInline(admin.TabularInline):
model = Essai_Temperature
form = Essai_TemperatureForm
ordering = ('-choix__lapropriete',)
extra = 5