Django multiple forms on one page with inherited models - python

For my current Django project I have built a page that uses 3 ModelForms.
The entire page is supposed to let hotels register on the site.
So the first ModelForm is from the Hotel model
Next I want the user to enter 2 forms for images, a main image and a background image. These 2 models are inherited from a basic Image class.
So in this case both images will use the title field, coming from the base class.
Now When I enter a new hotel into the form and also add the 2 images, both images get the same title.
When I take a look at the html code this does makes sense because the input fields both have the same name="title".
For the rest the form works just as expected, it is just this one issue.
Of course I could just take the title field and put them into the child classes as child_class_1_title and child_class_2_title, but that would just break the entire OOP and DRY principle.
How can I make sure that each form pushes the right data into the database?
Thanks a lot in advance.
This is my code for the forms:
class HotelForm(ModelForm):
class Meta:
model = Hotel
fields = ('name', 'address', 'zip_code', 'city', 'website', 'email', 'phone', 'description', 'short_description', 'number_of_rooms', 'facilities',
'activities', 'environment', 'hotel_type')
widgets = {
'facilities': CheckboxSelectMultiple()
}
class HotelGeneralImageForm(ModelForm):
class Meta:
model = HotelGeneralImage
fields = ('title', 'hotel_general_image')
widgets = {
'hotel_general_image': FileWidget()
}
class HotelBackgroundImageForm(ModelForm):
class Meta:
model = HotelBackgroundImage
fields = ('title', 'hotel_background_image')
widgets = {
'hotel_background_image': FileWidget()
}
And the view:
#csrf_exempt
def hotel_registration(request):
if request.method == 'POST':
hotel_form = HotelForm(request.POST, instance=Hotel())
hotel_general_image_form = HotelGeneralImageForm(request.POST, instance=HotelGeneralImage())
hotel_background_image_form = HotelBackgroundImageForm(request.POST, instance=HotelBackgroundImage())
if hotel_form.is_valid() and hotel_general_image_form.is_valid() and hotel_background_image_form.is_valid():
new_hotel = hotel_form.save()
new_hotel_general_image = hotel_general_image_form.save(commit=False)
new_hotel_general_image.hotel = new_hotel
new_hotel_general_image.save()
new_hotel_background_image = hotel_background_image_form.save(commit=False)
new_hotel_background_image.hotel = new_hotel
new_hotel_background_image.save()
return HttpResponseRedirect('registered')
else:
hotel_form = HotelForm(instance=Hotel())
hotel_general_image_form = HotelGeneralImageForm(instance=HotelGeneralImage())
hotel_background_image_form = HotelBackgroundImageForm(instance=HotelBackgroundImage())
context = {'hotel_form': hotel_form,
'hotel_general_image_form': hotel_general_image_form,
'hotel_background_image_form': hotel_background_image_form
}
context.update(csrf(request))
return render_to_response('hotel/hotel-registration-form.html', context)

Form prefix will solve your issue:
hotel_general_image_form = HotelGeneralImageForm(prefix='general', ...)
hotel_background_image_form = HotelBackgroundImageForm(prefix='background', ...)
This way each form will have its own prefix hence will not interfere with other forms.
More in docs - https://docs.djangoproject.com/en/1.9/ref/forms/api/#django.forms.Form.prefix

Related

Make a choices list for forms.py after receiving data from views.py in django

So this is the scenario. I allow my user to first input their vehicle brand with one form and then use another form to list down the models available for that vehicle brand. The information on the vehicles and the brands is stored in my database.
Refer to this image to get a better idea:
And this is my views.py:
def driver_dashboard_trip_brand (request, brand):
if request.method == "POST":
form = AddVehicleForm(request.POST)
else:
form = AddVehicleForm()
brands = VehicleBrand.objects.all()
context = {
"form":form,
"brands":brands,
"chosen_brand":brand
}
return render (request, "app/driver_dashboard.html", context)
And my forms.py:
class AddVehicleForm(forms.ModelForm):
model = forms.ModelChoiceField(queryset=VehicleModel.objects.all())
vehicle_colour = forms.ChoiceField(choices=COLOURS)
vehicle_number = forms.CharField(max_length=8, widget=forms.TextInput(attrs={'placeholder': 'eg: CAB-1234'}))
class Meta:
model = Vehicle
fields = ['model', 'vehicle_colour', 'vehicle_number']
So in order to set a query in the forms.py, I would first need to send the data from views.py to forms.py, and then I also need to do a query.
So my question is how can I query for all the car models from the VehicleModel database and create choices attribute for the form, once the user chooses the car brand.
My models.py...
class VehicleModel (models.Model):
brand = models.ForeignKey(VehicleBrand, on_delete=models.CASCADE)
model = models.CharField(max_length=30)
def __str__ (self):
return f"{self.brand} - {self.model}"
Its honestly not so hard, i kinda figured it out...
So this is my forms.py...
class AddVehicleForm(forms.ModelForm):
def __init__(self, brand=None, *args, **kwargs):
super(AddVehicleForm, self).__init__(*args, **kwargs)
self.fields['model'].queryset = VehicleModel.objects.filter(brand=brand)
model = forms.ModelChoiceField(queryset=VehicleModel.objects.all())
vehicle_colour = forms.ChoiceField(choices=COLOURS)
vehicle_number = forms.CharField(max_length=8, widget=forms.TextInput(attrs={'placeholder': 'eg: CAB-1234'}))
class Meta:
model = Vehicle
fields = ['model', 'vehicle_colour', 'vehicle_number']
class AddVehicleFormPost(forms.ModelForm):
model = forms.ModelChoiceField(queryset=VehicleModel.objects.all())
vehicle_colour = forms.ChoiceField(choices=COLOURS)
vehicle_number = forms.CharField(max_length=8, widget=forms.TextInput(attrs={'placeholder': 'eg: CAB-1234'}))
class Meta:
model = Vehicle
fields = ['model', 'vehicle_colour', 'vehicle_number']
Where the form AddVehicleForm allowed me to send the parameter as shown by typing form = AddVehicleForm(VehicleBrand.objects.filter(brand=brand).first()) in my views.py, but then when I wanted to save my form I needed to create another form in the forms.py without taking any query which is shown in AddVehicleFormPost.
Then i casually did,
if request.method == "POST":
form = AddVehicleFormPost(request.POST)
if form.is_valid():
In my views.py...
Here you have a nice tutorial on how to create dependent fields, you need to understand what's going on on the Server, and what's going on on the Client

How to get model data to appear as a field in another model's response

These are simplified versions of my models (the user model is just an id and name)
class Convo(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='convo_owner')
users = models.ManyToManyField(User, through='Convo_user')
class Convo_user (models.Model):
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
convo = models.ForeignKey(Convo, on_delete=models.CASCADE)
class Comments(models.Model):
name = models.CharField(max_length=255)
content = models.TextField(max_length=1024)
convo = models.ForeignKey(Convo, on_delete=models.CASCADE)
This is my view
class ConvoViewSet(viewsets.ModelViewSet):
serializer_class = serializers.ConvoSerializer
def get_queryset(self):
return None
def list(self, request):
curr_user = request.user.id
# Collecting the list of conversations
conversations = models.Conversation.object.filter(ConvoUser__user_id=request.user.id)
#Getting list of conversation id's
conv_ids = list(conversations.values_list('id', flat=True).order_by('id'))
#Getting list of relevant comments
comments = models.Comments.objects.filter(conversation_id__in=conv_ids)
return Response(self.get_serializer(conversations, many=True).data)
And my current serializer
class ConvoSerializer(serializers.ModelSerializer):
"""A serializer for messaging objects"""
# access = AccessSerializer(many=True)
# model = models.Comments
# fields = ('id', 'name', 'content', 'convo_id')
class Meta:
model = models.Convo
fields = ('id', 'owner_id')
The current response I get is of the form
[
{
"id": 1,
"owner_id": 32
}, ...
]
But I would like to add a comments field that shows all the properties of comments into the response, so basically everything in the second queryset (called comments) and I'm not sure how to go about this at all. (I retrieve the comments in the way I do because I'm trying to minimize the calls to the database). Would I need to create a new view for comments, make its own serializer and then somehow combine them into the serializer for the convo?
The way you've set up your models, you can access the comments of each Convo through Django's ORM by using convo_object.comments_set.all(), so you could set up your ConvoSerializer to access that instance's comments, like this:
class ConvoSerializer(serializers.ModelSerializer):
"""A serializer for messaging objects"""
comments_set = CommentSerializer(many=True)
class Meta:
model = models.Convo
fields = ('id', 'owner_id', 'comments_set')
and then you define your CommentSerializer like:
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = models.Comments
fields = ('id', 'name', 'content')
No data appears because my serializers are using the default database, not sure why but a step forward
EDIT:
Django: Database used for prefetch_related is not the same that the parent query Provided me the correct answer, I was able to choose the database with this method because for some reason inner queries use the default DB

only show a certain serializer field in a sub-level (newbie)

I'm slowly learning how to work with rest framework and I'm stuck in one part I don't really understand(my english isn't great either). I have this api point: building for which I show some data on api/building/ but I want a certain field to appear only on api/building/1 (1=pk number) and I cannot figure this out how.
Here is my serializer code so far:
class FloorSerializer(serializers.ModelSerializer):
class Meta:
model = Floor
fields = ('number',
'title')
class BuildingSerializer(serializers.HyperlinkedModelSerializer):
location = serializers.CharField(source='location_address')
avg_temperature = serializers.SerializerMethodField('_get_avg_temperature')
avg_humidity = serializers.SerializerMethodField('_get_avg_humidity')
occupancy_level = serializers.SerializerMethodField('_get_occupancy_level')
floors = FloorSerializer(many=True, read_only=True)
class Meta:
model = Building
fields = ('pk',
'title',
'image_url',
'location',
'campus_name',
'avg_temperature',
'avg_humidity',
'occupancy_level',
'floors')
def _get_avg_temperature(self, obj):
# magia filtrului per buildingu asta.
temp = SensorData.objects.filter(sensor__room__floor__building__pk=obj.pk).filter(sensor__type='TP')\
.aggregate(Avg('value'))
return temp
def _get_avg_humidity(self, obj):
# magia filtrului per buildingu asta.
hum = SensorData.objects.filter(sensor__room__floor__building__pk=obj.pk).filter(sensor__type='HU')\
.aggregate(Avg('value'))
return hum
def _get_occupancy_level(self, obj):
ocup = randint(45, 65)
return ocup
the field in question is floors. I want to show it only on api/building/pk level and while I read the documentation it is not quite clear to me.
Here is a great answer demonstrating what you should do:
https://stackoverflow.com/a/22755648/2402929
In summary, you should create a serializer that will contain all your methods and fields you want in the list route (/api/building/), then extend that serializer, adding the additional fields you want in the detail routes (/api/building/:pk)
Example:
class BuildingSerializer(serializers.HyperlinkedModelSerializer):
# additional methods for fields that will be
# inherited in the DetailBuildingSerializer
class Meta:
model = Building
fields = ('pk',
'title',
'image_url',
'location',
'campus_name',
'avg_temperature',
'avg_humidity',
'occupancy_level',
)
class DetailBuildingSerializer(BuildingSerializer):
class Meta:
model = Building
fields = ('pk',
'title',
'image_url',
'location',
'campus_name',
'avg_temperature',
'avg_humidity',
'occupancy_level',
'floors'
)
Later on, separate serializers in your viewset (assuming you are using viewsets):
class BuildingViewset(viewsets.ModelViewSet):
def get_serializer_class(self):
if self.action == 'list':
return serializers.BuildingSerializer
if self.action == 'retrieve':
return serializers.BuildingDetailSerializer

Django Form is not Valid on Model Form

I'm still learning Django. I have a wizard like form workflow where each form is filling out information in succession.
I have a model form that represents what I want inputted from the form:
Models.py
class AccountParameters(models.Model):
acctFilterName = models.ForeignKey(AccountFilters)
excludeClassification = models.ManyToManyField(ClassificationNames)
tradingCash = models.FloatField()
Forms.py
This is a dumbed down form and doesn't represent the real implementation. It was for testing.
class AccountSelectionForm(forms.ModelForm):
acctFilterName = forms.ModelChoiceField(queryset=AccountFilters.objects.all().values_list('acctFilterName', flat=True),
label="Account Filters:",
empty_label="Select Here..."
)
excludeClassification = forms.ModelMultipleChoiceField(queryset=ClassificationNames.objects.all().values_list('classificationName', flat=True),
label="Classifications Exclusion:"
)
tradingCash = forms.IntegerField(label="Remove accounts whose trading cash < % of AUM")
class Meta:
model = AccountParameters
fields =['excludeClassification', 'tradingCash',]
exclude = ['acctFilterName']
labels = {
'acctFilterName': _('Account Filters:')
}
views.py
def accountSelections(request): # NewRebalnce 2: AccountParameters with Accounts and Trading Form
if request.method == "POST":
form = AccountSelectionForm(request.POST)
if form.is_valid():
accountParameters = AccountParameters
#tradingCash = form.cleaned_data['tradingCash']
return render(request, 'NewRebalance_3.html', {'model_selection_form': ModelSelectionForm()})
else:
form = AccountSelectionForm()
return render(request, 'NewRebalance2.html', {'account_selections_form': form})
I'm not sure that I'm using Modelforms correctly. What I needed was a way to create a select drop down for my acctFilterName so I created the query set manually.
When I save the form it's not valid and in the form cleaned data I get the following:
Notice that the cleaned data only has the tradingCash field.
What am I doing wrong? Is there a better way to do this? Why is the Form in valid?
try to remove .values_list('acctFilterName', flat=True) from the ModelChoiceField and remove .values_list('classificationName', flat=True) from the ModelMultipleChoiceField.
They should be like this:
acctFilterName = forms.ModelChoiceField(
queryset=AccountFilters.objects.all(),
label="Account Filters:",
empty_label="Select Here...")
excludeClassification = forms.ModelMultipleChoiceField(
queryset=ClassificationNames.objects.all(),
label="Classifications Exclusion:")
.values_list will strip your queryset of all the data the ModelChoiceField needs to recognize the item, leaving only the model fields you specify as an argument. Namely, what you're removing here is the primary key of the model which is fundamental.
Why are you redeclaring the fields on your AccountSelectionForm ModelForm???
Better this way:
class AccountSelectionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.fields['excludeClassification'].queryset = ClassificationNames.objects.all().values_list('classificationName', flat=True)
self.fields['acctFilterName'].queryset = AccountFilters.objects.all().values_list('acctFilterName', flat=True)
class Meta:
model = AccountParameters
fields =['excludeClassification', 'tradingCash',]
exclude = ['acctFilterName']
labels = {
'acctFilterName': _('Account Filters:')
}

A way to show/hide a field on Django

I have this as my list display in Django admin
list_display = ('product', 'price', 'purchase_date', 'confirmed', 'get_po_number', 'notes')
in models.py:
class PurchaseOrder(models.Model):
notes = models.TextField( null=True, blank= True)
This is what it looks like here:
[1]: http://i.imgur.com/ZyKmpoF.png '
As you can see 'notes' could take up a lot of room, so is there a way I can view/hide that field with the click of a button?
Instead of doing a button, you can resize the field to become smaller.
class PurchaseAdmin(admin.ModelAdmin):
formfield_overrides = {
models.CharField: {'widget': TextInput(attrs={'size':'20'})},
models.TextField: {'widget': Textarea(attrs={'rows':4, 'cols':40})},
}
admin.site.register(PurchaseOrder, PurchaseAdmin)
If you really want to have another button, you can use your custom Inline class to define the fields:
class CustomInline(admin.TabularInline):
readonly_fields = [...'link',...]
# important part which define what "link" looks like
def link(self, instance):
url = # your link to display the note
return mark_safe(u'View Note".format(u=url))
And in your custom admin class, use this Inline class instead:
class PurchaseAdmin(admin.ModelAdmin):
inlines = [CustomInline]

Categories

Resources