I am building an app where managers can create a private webpage, they need to add people manually in order for them to access the page.
I don't want the managers to see all of the users, So I would like to render only the PK in the list.
My views.py
class HotelCreateView(LoginRequiredMixin, CreateView):
model = Hotel
form_class = HotelForm
def form_valid(self, form):
form.instance.manager_hotel = self.request.user
return super().form_valid(form)
forms.py
from django.db import models
from django.forms import ModelForm
from .models import Hotel
class ColleagueChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.get_pk()
class HotelForm(models.Model):
ColleagueModelChoiceField(queryset=Colleague.objects.filter(pk))
You're setting fields on your CreateView, so you're letting Django generate the ModelForm automatically for you. The form uses a ModelMultipleChoiceField, which derives from ModelChoiceField, described here.
If you read the last paragraph of that section, you'll see that the display values for such a field are coming from the model's __str__ method, or you can override this with the label_from_instance() method.
That's therefore what you need to do, override this method on the ModelMultipleChoiceField. But to do that, you need to specify your own form.
So:
Create your own ModelForm for your Hotel model (HotelForm).
Create a subclass of ModelMultipleChoiceField (ColleagueChoiceField) and override the label_from_instance() method to display the pk.
Set the colleagues field on the HotelForm to be a ColleagueChoiceField.
Remove the fields attribute on your view and set the form_class to your HotelForm instead.
I am a beginner programming in Django framework and I am learning how to implement a CreateView (a class based view for creating a form based on a model) in my views.py file.
No, you don't the view will automatically create a model form for you, but you have to option of overwriting it.
Let's assume you have MyModel, you can do this:
from myapp.models import MyModel
# views.py
class MyCreateView(CreateView):
model = MyModel
fields = ['something', 'somethingelse'] # these are fields from MyModel
If you do not specify the fields Django will throw an error.
If you want to customize your form validation in some way, you can do this:
# forms.py
class MyForm(ModelForm):
class Meta:
model = MyModel
fields = ['something'] # form fields that map to the model
# ... do some custom stuff
# views.py
class MyCreateView(CreateView):
model = MyModel
form_class = MyForm
Notice that we are not specifying the fields anymore on MyView because if we would it will also throw an error, and the reasons is because the view will fetch the fields from the form.
More information: https://docs.djangoproject.com/en/2.1/topics/class-based-views/generic-editing/
Code that handles the form_class: https://github.com/django/django/blob/master/django/views/generic/edit.py#L74
You don't need to create a ModelForm, you just need to specify the model in the model attribute, e.g. for an Author model set model = Author.
CreateView uses the ModelFormMixin, which uses this model attribute to handle the ModelForm:
from django.views.generic.edit import CreateView
from myapp.models import Author
class AuthorCreate(CreateView):
model = Author
fields = ['name']
See more here: https://docs.djangoproject.com/en/2.1/ref/class-based-views/mixins-editing/#django.views.generic.edit.ModelFormMixin.model
I have model which among other fields contains price property which is calculated dynamically. I want to display this property in model admin page. So I've created custom ModelForm:
class ShipmentForm(forms.ModelForm):
price = forms.IntegerField()
class Meta:
model = models.Shipment
fields = [
'title',
'price',
]
However I can't get price value in that form.
Here's how I change form in admin panel:
class ShipmentAdmin(admin.ModelAdmin):
form = ShipmentForm
See ModelAdmin.readonly_fields (Django 1.10 docs link).
A read-only field can not only display data from a model’s field, it can also display the output of a model’s method or a method of the ModelAdmin class itself.
This means that you can do:
class ShipmentAdmin(admin.ModelAdmin):
readonly_fields = ('price', 'something_else')
def something_else(self, instance):
# calculate something else here
Where price is a model method, and something_else is a ModelAdmin method.
I have two form classes, each of which are a ModelForm of the same model. One is called ProjectForm and the other is called AdminProjectForm. ProjectForm has a number of fields excluded, AdminProjectForm does not.
In my views, I'm using two classes, Edit and AdminEdit. Edit uses the Django generic editing view of UpdateView and has the form_class set to ProjectForm. AdminEdit is a inherits Edit and has the form_class set to AdminProjectEdit.
One would think this would mean that the form generated by AdminEdit would thus show the fields that are excluded on Edit. This part is working correctly - the form fields are drawn perfectly fine (and not drawn on Edit. However, when submitting the AdminEdit form, any field excluded in ProjectForm is stripped and not saved. Any suggestions?
Here's my forms.py:
class ProjectForm(ModelForm):
class Meta:
model = Project
exclude = ('field1', 'field2', 'field3', 'qualifies_for_judging', 'reason_for_disqualification', 'finalist', 'hashtag')
class AdminProjectForm(ModelForm):
class Meta:
model = Project
exclude = ()
And my views.py:
class Edit(UpdateView):
model = Project
form_class = ProjectForm
class AdminEdit(Edit):
model = Project
form_class = AdminProjectForm
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