There are several questions on stackoverflow related to this topic but none of them explains whats happening neither provides working solution.
I need to pass user's first name as an argument to Django ModelForm when rendering template with this form.
I have some basic form:
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.first_name = ???
super(MyForm, self).__init__(*args, **kwargs)
class Meta:
model = MyModel
fields = [***other fields***, 'first_name']
Here's my sample class-based view:
class SomeClassBasedView(View):
def get(self, request):
user_first_name = request.user.first_name
form = MyForm(user_first_name)
return render(request, 'my_template.html', {'form': form})
What do I pass to MyForm when initialising it and how do I access this value inside the __init__ method?
I want to use 'first_name' value as a value for one of the fields in template by updating self.fields[***].widget.attrs.update({'value': self.first_name}).
The solution is as simple as passing initial data dictionary to MyForm for fields you need to set initial value.
This parameter, if given, should be a dictionary mapping field names to initial values. So if MyModel class has a field my_field and you want to set its initial value to foo_bar then you should create MyForm object with initial parameters as follows:
form = MyForm(initial={'my_field': 'foo_bar'})
A link to Django documentation on this topic.
You can pass the parameter instance to your form like this:
obj = MyModel(...)
form = MyForm(instance=obj)
If obj has a user first name it will be attached to your form.
Related
I can alternatively create different types of form, but that's tedious.
So is it possible to pass the type to the form,then show the form accordingly?
This code shows NameError: name 'review_type' is not defined
class Contest1_for_review(ModelForm, review_type):
class Meta:
model = Contest1
decision = review_type + '_decision'
comment = review_type +'comment'
fields = [
decision,
comment,
]
Is it possible to pass a argument to meta class, like this?
Form is a class and when its rendered in the HTML, its rendering an instance of the form class. So when passing a value to that instance, you can use its __init__ method. For example:
class Contest1_for_review(ModelForm):
def __init__(self, *args, **kwargs):
review_type = kwargs.pop('review_type') # <-- getting the value from keyword arguments
super().__init__(*args, **kwargs)
self.fields[f'{review_type}_decision'] = forms.CharField()
self.fields[f'{review_type}_comment'] = forms.CharField()
class Meta:
model = Contest1
fields = "__all__"
Also, you need to send the value of review_type from view to form. Like this in function based view:
form = Contest1_for_review(review_type="my_value")
Or use get_form_kwargs to send the value from a Class based view. FYI: you don't need to change anything in Meta class.
Update:
From discussion in comments, OP should use forms.Form instead of ModelForm as using model form requires fields /exclude value in Meta class.
I have a Django admin class which declares an inlines iterable. Something like:
#admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
...
...
inlines = [CategoryModifiersInline,]
...
...
Then I have an Inline admin class like this:
class CategoryModifiersInline(admin.TabularInline):
model = Category.modifiers.through
fk_name = 'category'
extra = 1
def formfield_for_foreignkey(self, db_field, request, **kwargs):
qs = Product.objects.filter(is_modifier=True).filter(active=True)
kwargs['queryset'] = qs
return super(CategoryModifiersInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Where I filter the queryset for the foreign key based on some business requirement.
This inline is only showed to the user in the change view, that means, when an object of the class Category is created and the user wants to add modifiers to it, never in the add view.
What I want to do, is filtering the foreign key by one of the attributes of the Category model, I mean, I want to access the parent object from the formfield_for_foreignkey method.
Does anyone know a way to achieve that?
Well I found a similar question here in StackOverflow, and used the method described there to solve it.
It uses the parent_model attribute from inlines, and the resolve method from django.core.urlresolvers to get the instance based in the url.
Here's the code:
def get_object(self, request):
resolved = resolve(request.path_info)
if resolved.args:
return self.parent_model.objects.get(pk=resolved.args[0])
return None
Then I would call the get_object method inside of my formfield_from_foreignkey method to get the instance of the object I want to use as a filter.
Hope it helps!
I want to connect each post with the logged in user who posted it.
models.py
from django.conf import settings
from django.db import models
# Create your models here.
class Campagin(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1)
title = models.CharField(max_length=120)
media = models.FileField()
description = models.TextField(max_length=220)
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
def __str__(self):
return self.title`
As you can see the posts were made by two different users, but the relation shows that it is made by the first user
this image shows the registered users..
Views.py
class NewCampagin(LoginRequiredMixin, CreateView):
template_name = 'campagin/new_campagin.html'
model = Campagin
fields = ['title','media','description']
def get_absolute_url(self):
return reverse('campagin:active_campagin')
Okay so CreateView allows you to specify the model and fields attributes to implicitly create a form for you. It's quite neat for quick form submissions but in your case, you will need to make some customizations before saving the Campaign object into the database (linking up the current logged in user).
As a result, you will need to create your own form first (create a file called forms.py which can be next to your views.py) and enter this code:
class CampaignForm(ModelForm): # Import ModelForm too.
def __init__(self, *args, **kwargs):
# We need to get access the currently logged in user so set it as an instance variable of CampaignForm.
self.user = kwargs.pop('user', None)
super(CampaignForm, self).__init__(*args, **kwargs)
class Meta:
model = models.Campaign # you need to import this from your models.py class
fields = ['title','media','description']
def save(self, commit=True):
# This is where we need to insert the currently logged in user into the Campaign instance.
instance = super(CampaignForm, self).save(commit=False)
# Once the all the other attributes are inserted, we just need to insert the current logged in user
# into the instance.
instance.user = self.user
if commit:
instance.save()
return instance
Now that we have our forms.py all ready to go we just need to modify your views.py:
class NewCampagin(LoginRequiredMixin, CreateView):
template_name = 'campagin/new_campagin.html'
form_class = forms.CampaignForm # Again, you'll need to import this carefully from our newly created forms.py
model = models.Campaign # Import this.
queryset = models.Campaign.objects.all()
def get_absolute_url(self):
return reverse('campagin:active_campagin') # Sending user object to the form, to verify which fields to display/remove (depending on group)
def get_form_kwargs(self):
# In order for us to access the current user in CampaignForm, we need to actually pass it accross.
# As such, we do this as shown below.
kwargs = super(NewCampaign, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
What's actually happening with my POST requests under the bonnet??
Note: This is just extra information for the sake of learning. You do
not need to read this part if you don't care about how your class
based view is actually handling your post request.
Essentially CreateView looks like this:
class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView):
"""
View for creating a new object instance,
with a response rendered by template.
"""
template_name_suffix = '_form'
Doesn't look that interesting but if we analyse BaseCreateView:
class BaseCreateView(ModelFormMixin, ProcessFormView):
"""
Base view for creating an new object instance.
Using this base class requires subclassing to provide a response mixin.
"""
def post(self, request, *args, **kwargs):
self.object = None
return super(BaseCreateView, self).post(request, *args, **kwargs)
we can see we are inheriting from two very important classes ModelFormMixin and ProcessFormView. Now the line, return super(BaseCreateView, self).post(request, *args, **kwargs), essentially calls the post function in ProcessFormView which looks like this:
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
As you can see, your CreateView really just boils down to this small post function which simply gets a specified form and validates + saves it. There's 2 questions to ask at this point.
1) What does form = self.get_form() do since I didn't even specify my form?
2) What is self.form_valid(form) actually doing?
To answer the first question, self.get_form() essentially calls another function form_class = self.get_form_class() and this function is actually found in ModelFormMixin (the one where inherited from!):
def get_form_class(self):
"""
Returns the form class to use in this view.
"""
if self.fields is not None and self.form_class:
raise ImproperlyConfigured(
"Specifying both 'fields' and 'form_class' is not permitted."
)
if self.form_class:
return self.form_class
else:
if self.model is not None:
# If a model has been explicitly provided, use it
model = self.model
elif hasattr(self, 'object') and self.object is not None:
# If this view is operating on a single object, use
# the class of that object
model = self.object.__class__
else:
# Try to get a queryset and extract the model class
# from that
model = self.get_queryset().model
if self.fields is None:
raise ImproperlyConfigured(
"Using ModelFormMixin (base class of %s) without "
"the 'fields' attribute is prohibited." % self.__class__.__name__
)
# THIS IS WHERE YOUR FORM WAS BEING IMPLICITLY CREATED.
return model_forms.modelform_factory(model, fields=self.fields)
As you can see, this function is where your form was being implicitly created (see very last line). We needed to add more functionality in your case so we created our own forms.py and specified form_class in the views.py as a result.
To answer the second question, we need to look at the function (self.form_valid(form)) call's source code:
def form_valid(self, form):
"""
If the form is valid, save the associated model.
"""
# THIS IS A CRUCIAL LINE.
# This is where your actual Campaign object is created. We OVERRIDE the save() function call in our forms.py so that you could link up your logged in user to the campaign object before saving.
self.object = form.save()
return super(ModelFormMixin, self).form_valid(form)
So here we are simply saving the object.
I hope this helps you!
More information at https://docs.djangoproject.com/en/1.10/ref/class-based-views/generic-editing/#createview
I'd like to fill one field of my model automatically. It holds a client IP.
I've defined an CreateView as follows:
class MyView(CreateView):
def post(self, request, *args, **kwargs):
self.form_class.client_ip = request.META.get('REMOTE_ADDR')
super(MyView, self).post(request, *args, **kwargs)
model = MyModel
form_class = MyForm
and MyForm in that way:
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
exclude = ('ip',)
And I have no idea how to fill this exluded field.
In MyView you should add a method called get_initial which returns the initial values of the form (as a dictionary). For example:
def get_initial(self):
return { 'ip': ... }
These initial values are then used when the form is created.
excluded fields have be to manually filled. you can look into the middleware processors that can add to the request.POST dict and you can override the __init__ on the MyForm, [first call super] and then set the model's ip field.
the middleware processor can also add to POST some other request level attributes, making it available for future use.
A Django autofield when displayed using a formset is hidden by default. What would be the best way to show it?
At the moment, the model is declared as,
class MyModel:
locid = models.AutoField(primary_key=True)
...
When this is rendered using Django formsets,
class MyModelForm(ModelForm):
class Meta:
model = MyModel
fields = ('locid', 'name')
it shows up on the page as,
<input id="id_form-0-locid" type="hidden" value="707" name="form-0-locid"/>
Thanks.
Edit
I create the formset like this -
LocFormSet = modelformset_factory(MyModel)
pformset = LocFormSet(request.POST, request.FILES, queryset=MyModel.objects.order_by('name'))
Second Edit
Looks like I'm not using the custom form class I defined there, so the question needs slight modification..
How would I create a formset from a custom form (which will show a hidden field), as well as use a custom queryset?
At the moment, I can either inherit from a BaseModelFormSet class and use a custom query set, or I can use the ModelForm class to add a custom field to a form. Is there a way to do both with a formset?
Third Edit
I'm now using,
class MyModelForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
locid = forms.IntegerField(min_value = 1, required=True)
self.fields['locid'].widget.attrs["type"] = 'visible'
self.queryset = MyModel.objects.order_by('name')
class Meta:
model = MyModel
fields = ('locid', 'name')
LocFormSet = modelformset_factory(MyModel, form = MyModelForm)
pformset = LocFormSet()
But this still doesn't
Show locid
Use the custom query that was specified.
Try changing the default field type:
from django import forms
class MyModelForm(ModelForm):
locid = forms.IntegerField(min_value=1, required=True)
class Meta:
model = MyModel
fields = ('locid', 'name')
EDIT: Tested and works...
As you say, you are not using the custom form you have defined. This is because you aren't passing it in anywhere, so Django can't know about it.
The solution is simple - just pass the custom form class into modelformset_factory:
LocFormSet = modelformset_factory(MyModel, form=MyModelForm)
Edit in response to update 3:
Firstly, you have the redefinition for locid in the wrong place - it needs to be at the class level, not inside the __init__.
Secondly, putting the queryset inside the form won't do anything at all - forms don't know about querysets. You should go back to what you were doing before, passing it in as a parameter when you instantiate the formset. (Alternatively, you could define a custom formset, but that seems like overkill.)
class MyModelForm(ModelForm):
locid = forms.IntegerField(min_value=1, required=True)
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.fields['locid'].widget.attrs["type"] = 'visible'
class Meta:
model = MyModel
fields = ('locid', 'name')
LocFormSet = modelformset_factory(MyModel, form = MyModelForm)
pformset = LocFormSet(request.POST, request.FILES,
queryset=MyModel.objects.order_by('name')))
Okay, none of the approaches above worked for me. I solved this issue from the template side, finally.
There is a ticket filed (http://code.djangoproject.com/ticket/10427), which adds a "value" option to a template variable for a form. For instance, it allows,
{{form.locid.value}}
to be shown. This is available as a patch, which can be installed in the SVN version of django using "patch -p0 file.patch"
Remember, the {{form.locid.value}} variable will be used in conjunction with the invisible form - otherwise, the submit and save operations for the formset will crash.
This is Not the same as {{form.locid.data}} - as is explained in the ticket referred to above.
The reason that the autofield is hidden, is that both BaseModelFormSet and BaseInlineFormSet override that field in add_field. The way to fix it is to create your own formset and override add_field without calling super. Also you don't have to explicitly define the primary key.
you have to pass the formset to modelformset_factory:
LocFormSet = modelformset_factory(MyModel,
formset=VisiblePrimaryKeyFormSet)
This is in the formset class:
from django.forms.models import BaseInlineFormSet, BaseModelFormSet, IntegerField
from django.forms.formsets import BaseFormSet
class VisiblePrimaryKeyFormset(BaseModelFormSet):
def add_fields(self, form, index):
self._pk_field = pk = self.model._meta.pk
if form.is_bound:
pk_value = form.instance.pk
else:
try:
pk_value = self.get_queryset()[index].pk
except IndexError:
pk_value = None
form.fields[self._pk_field.name] = IntegerField( initial=pk_value,
required=True) #or any other field you would like to display the pk in
BaseFormSet.add_fields(self, form, index) # call baseformset which does not modify your primary key field