I was wondering if there is a way to make this more optimised.
class AppTopic(models.Model):
topic = models.CharField(max_length=20) # lowercase (cookies)
class UserTopic(models.Model):
topic = models.CharField(max_length=20) # Any case (cOoKiEs)
app_topic = models.ForeignKey(AppTopic) # related to (cookies -> lowercase version of cOoKiEs)
class User(models.Model):
topics = models.ManyToManyField(UserTopic) # AppTopic stored with any case
The goal is to have all AppTopics be lowercase on the lowest level, but I want to allow users to chose what capitalisation they want. In doing so I still want to keep the relation with it's lowercase version. The topic on the lowest level could be cookies, the user might pick cOoKiEs which should still be related to the orignal cookies.
My current solution works, but requires an entirely new table with almost no use. I will continue using this if there isn't really a smarter way to do it.
There's nothing in the Django model API that would allow you to manipulate values from the models themselves. If you don't want to change the value in the database or in the model instance, you can change how it displays on the template level using the
lower filter in Django template language.
<body>
<div>
{% if condition %}
<h1>{{AppTopic|lower}}</h1>
{% else %}
<h1>{{AppTopic|??}}</h1> #AppTopic but camelcase. There's no camelcase filter in Django
{% endif %}
</div>
</body
Views.py
def View(request, condition)
...
return render(request, 'template.html', {condition:'condition'})
Related
I want to change my Foreign Key to Many To Many field to let the user select multiple categories in a dropdown list.
This is what I already have. After I change Foreign Key to Many To Many I'm getting milion errors, I have to get rid of on_delete=models.CASCADE which is a core of my app. What can I do? Which way should I take? Maybe add another model? I'm so confused, especially when I am a Django newbie. Thank you for your help!
MODELS
class Category(models.Model):
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return f'{self.name}'
class Expense(models.Model):
class Meta:
ordering = ('date', '-pk')
category = models.ForeignKey(Category, null=True,blank=True, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
amount = models.DecimalField(max_digits=8,decimal_places=2)
date = models.DateField(default=datetime.date.today,db_index=True)
def __str__(self):
return f'{self.date} {self.name} {self.amount}'
The clue of the application is to let the user create a category e.g "PC". Then add some expenses like "GPU", "CPU" etc... and let the user link it to the "PC" category. And when the user wants to delete certain categories, all the expenses linked to it, gonna be deleted too. And this is the thing I have already did. BUT NOW I want to let the user search the main table of expenses by multiple categories. And here comes my problem, I don't have a clue how to do it and keep the whole application in one piece with all the functionalities.
SCREENSHOTS:
Categories View with just added PC category
Expense Add View
I don't think there is a simple answer to your question, but here are some resources that might help. First, I don't think you should change your models. From the way you described your application, I think a foreign key model with on_delete=CASCADE is good. The basic idea here is that you need to change your list view function so that it performs a query of your database. Also modify your template.html to include a search bar.
https://github.com/csev/dj4e-samples/tree/master/well
https://www.dj4e.com/lessons/dj4e_ads4
Modify Your List View To Allow The Searching
This is an example of a list view that allows you to search for a single term, and returns anything in the database that matches from any field. This isn't what you want to do exactly, but if you can get this working then you can modify the search conditions for your specific application. What is going on in the code below is that instead of return every item in my Ad table in my SQL database, I filter it based on the search. Then, I pass "ad_list" to the template view. Since I already filtered ad_list based on the search, in the template view it will only list the items that match. This is based on the DJ4E course, and you can watch the video there to get an idea of how he implements the search bar better.
from ads.models import Ad
from django.views import View
from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse_lazy, reverse
from django.http import HttpResponse
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.contrib.humanize.templatetags.humanize import naturaltime
from ads.utils import dump_queries
from django.db.models import Q
class AdListView(ListView):
# By convention:
template_name = "ads/ad_list.html"
def get(self, request) :
strval = request.GET.get("search", False)
if strval :
# Simple title-only search
# objects = Ad.objects.filter(title__contains=strval).select_related().order_by('-updated_at')[:10]
# Multi-field search
query = Q(title__contains=strval)
query.add(Q(text__contains=strval), Q.OR)
objects = Ad.objects.filter(query).select_related().order_by('-updated_at')[:10]
else :
# try both versions with > 4 posts and watch the queries that happen
objects = Ad.objects.all().order_by('-updated_at')[:10]
# objects = Ad.objects.select_related().all().order_by('-updated_at')[:10]
# Augment the post_list
for obj in objects:
obj.natural_updated = naturaltime(obj.updated_at)
ctx = {'ad_list' : objects, 'search': strval}
retval = render(request, self.template_name, ctx)
dump_queries()
return retval;
Modify Your Template.html to include a search bar
<form>
<input type="text" placeholder="Search.." name="search"
{% if search %} value="{{ search }}" {% endif %}
>
<button type="submit"><i class="fa fa-search"></i></button>
<i class="fa fa-undo"></i>
</form>
PS, I think you can answer your own question better when you figure it out, so help others and post it!
I would like to show possible choices from ManyToManyField (which I have in Homes model) in the Owners form. I have Owners <--Many2Many--> Homes with custom class HomesOwners. In Homes it works out of the box, I don't know how to make it work in Owners.
I am using Django 2.2.4 with Bootstrap 4 and Postgresql. I started my project based on django-bookshelf project (also just Django and Bootstrap4). I do not use any render. Comment in django-bookshelf project mentioned How to add bootstrap class to Django CreateView form fields in the template?, so I stick to that if it comed to forms.
I'm pretty new to Python (so Django too) and web technologies in general. I googled dozen of different questions/answers but I couldn't find any nice explanation of what is what and how to use it in real life. Most of them ended up with basic usage.
I did some experimentation on my own, but no success so far...
Here is the code
I have two models - Homes/models.py and Owners/models.py
Homes/models.py:
class Homes(models.Model):
id = models.AutoField(primary_key=True)
# other fields
some_owners = models.ManyToManyField(Owners, through='HomesOwners', through_fields=('id_home', 'id_owner'), related_name='some_owners')
# end of fields, some other code in the class like "class Meta" etc.
class HomesOwners(models.Model):
id = models.AutoField(primary_key=True)
id_home = models.ForeignKey(Homes, models.DO_NOTHING, db_column='id_home')
id_owner = models.ForeignKey('owners.Owners', models.DO_NOTHING, db_column='id_owner')
Owners/models.py do not have anything special, no imports from my Homes/models.py etc.
and my forms:
Homes/forms.py:
class HomesForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(HomesForm, self).__init__(*args, **kwargs)
self.fields['some_field_from_homes_model'].widget.attrs = {'class': 'form-control '}
#
# --> no need self.fields for M2M, Django does the work
#
# but I tried also and have a --> Question 2
# self.fields["some_owners"].widget = forms.widgets.CheckboxSelectMultiple()
# self.fields["some_owners"].queryset = HomesOwners.objects.all()
Without any code as "self.fields" for M2M field, Django is able to generate for me list of owners.
Question 1
I would like to get list of Homes in my OwnersForms.
I do not know what to add. I assume that I cannot add
# Owners/models.py
some_homes = models.ManyToManyField(Homes, through='HomesOwners', through_fields=('id_home', 'id_owner'), related_name='some_homes')
because of circular import, am I right?
How do I get my Homes list using self.fields?
What do I need to add to my code?
Question 2
When I've added
# Homes/forms.py
self.fields["some_owners"].widget = forms.widgets.CheckboxSelectMultiple()
self.fields["some_owners"].queryset = HomesOwners.objects.all()
I got
<!-- html page -->
HomesOwners object (1)
HomesOwners object (2)
<!-- and so on... -->
How can I just list Owners?
How to filter/order them so first they would appear Owners not connected to any Home?
Question 3
class HomesOwners(models.Model):
id = models.AutoField(primary_key=True)
id_home = models.ForeignKey(Homes, models.DO_NOTHING, db_column='id_home')
id_owner = models.ForeignKey('owners.Owners', models.DO_NOTHING, db_column='id_owner')
def __str__(self):
return pass #return something
I can't get my head around this. This class connects Homes and Owners. When I'm thinking of Homes I would like to return Owners and vice versa. So it should return different things depending on what object we are using (home or owner). I think this is connected to my 2nd question about:
HomesOwners object (1)
Also...
In homes.html I'm using my M2M like that:
{% for owner in homes.homesowners_set.all %}
{{ owner.id_owner.id }}
{% endfor %}
I would like to write something similar to my owners.html and list homes. This is connected to my previous question, I would like to have full answer if that's possible.
EDIT
With the answer given to me I was able to add Homes to OwnerUpdate view. I have views like that:
owners/views.py
# List View
class OwnersList(ListView):
model = Owners
# Detail View
class OwnersView(DetailView):
model = Owners
# Create View
class OwnersCreate(CreateView):
model = Owners
form_class = OwnersForm
# Setting returning URL
success_url = reverse_lazy('owners_list')
# Update View
class OwnersUpdate(UpdateView):
model = Owners
form_class = OwnersForm
success_url = reverse_lazy('owners_list')
# Delete View
class OwnersDelete(DeleteView):
model = Owners
success_url = reverse_lazy('owners_list')
What change do I need to make to be able to show in OwnersList Homes they own?
In Homes DetailView I am able to show Owners. I would like to do the same for Homes' DetailView and Homes ListView.
I don't really get it what you asking for,
but if I understand your question correctly,
I assume you want to add Homes list (not HomesOwners list) into your Owners form, right?
you can add extra field in your form like this:
class OwnersForm(ModelForm):
# notice the queryset is 'Homes' not 'HomesOwners'
homes = forms.ModelMultipleChoiceField(queryset=Homes.objects.all(), widget=forms.CheckboxSelectMultiple)
class Meta:
model = Owners
fields = ('homes', 'your_other_fields',)
# then you can access in init function too if you want
def __init__(self, *args, **kwargs):
super(OwnersForm, self).__init__(*args, **kwargs)
self.fields['homes'].required = False
then, since it using CheckboxSelectMultiple widget, you can iterate it in your html template like this:
{% for key, value in your_form.homes.field.choices %} <!-- notice '.field.'-->
{{ key }} = {{ value }}
{% endfor %}
you probably need to create custom save too for your form.
for your question 3, it is not about the form?
If you want to show HomesOwners, you are already doing right.
{% for owner in homes.homesowners_set.all %}
{{ owner.id_owner.id }}
{% endfor %}
but it will work if that homes is only 1 object.
if homes is a queryset, you have to iterate it first
{% for home in homes %}
{% for owner in home.homesowners_set.all %}
{{ owner.id_owner.id }}
{% endfor %}
{% endfor %}
sorry if I misunderstanding your questions,
maybe you can provide your views.py too, so I or others can help you more specific
I have been dabbling with Django CBV lately and have a question. Maybe you have better ideas than I.
Assume I have a airline booking CRM application and I intend to perform a display of a customer for various things. Assume I have a list of Models, for a Customer like Booking, Rating, Customer_Service_Calls, Favourited_Route.
Now, given a DetailView implemented by Django's CBV, I have something like this
class CustomerThreeSixtyView(DetailView):
model = 'Customer'
def get_context_data(self, **kwargs):
context = super(CustomerThreeSixtyView, self).get_context_data(**kwargs)
context['bookings'] = Booking.objects.all.filter(customer_id=request.kwargs['pk']
context['ratings'] = Ratings.objects.all.filter(customer_id=request.kwargs['pk']
context['calls'] = Customer_Service_Calls.objects.all.filter(customer_id=request.kwargs['pk'], status'Open')
context['fav_routes'] = Favourited_Route.objects.all.filter(customer_id=request.kwargs['pk'], status'Open')
return context
Something like this. My question is that, are there better ways to do this? This is the most straightforward way but I'm asking for suggestions because there seem to be bound for something.
What you have done already looks good enough. You are getting what you required in the context and then using it in the template to show the information.
Alternatively, you could directly access bookings for a particular customer in the template without specifying it in the context:
{% for booking in object.booking_set.all %} # object is the customer here
# do what you want to do with the booking here
{% endfor %}
It is even better if you use related_name while linking the customer to Booking:
class Booking(models.Model):
customer = models.ForeignKey(Customer, related_name='bookings')
# other fields
Now, you can directly use the defined related_name to access the bookings for a particular customer:
{% for booking in object.bookings.all %}
# do what you want to do with the booking here
{% endfor %}
And, you can use the same approach for other classes such as Rating, Customer_Service_Calls, Favourited_Route etc.
There is a common case I encounter, where I can't find a way to split apps.
The case is when a info of two models is related and needs to be in the same template
An example speaks 1000 words: (2 models - pages + comments).
# models.py
class Page(models.Model):
title = models.CharField()
content = models.TextField()
class Comment(models.Model):
page = models.ForeignKey('Page')
content = models.TextField()
# url.py
...
url(r'^page/(?P<page_pk>\d+)/$', views.ViewPage, name='page-view-no-comments'),
url(r'^comment/(?P<comment_pk>\d+)/$', views.ViewComment, name='comment-view'),
url(r'^page-with-comments/(?P<page_pk>\d+)/$', views.ViewPageWithComments, name='page-view-with-comments'),
...
# views.py
def ViewPage(request, page_pk):
page = get_object_or_404(Page, pk=page_pk)
return render(request, 'view_page.html', {'page':page,})
def ViewComment(request, comment_pk):
comment = get_object_or_404(Comment, pk=comment_pk)
return render(request, 'view_comment.html', {'comment':comment})
def ViewPageWithComments(request, page_pk):
page = get_object_or_404(Page, pk=page_pk)
page_comments = Comment.objects.filter(page=page)
return render(request, 'view_page.html', {'page':page,'page_comments':page_comments'})
In this situation, splitting to Page app and Comment app is problematic, because they share a view (ViewPageWithComments) and url.
My options are:
1) Create an Ajax call to comments, which has crawling problems although Google might have fixed it lately.
2) Create a method of page that calls a method in the comments app that returns html with the comments content. If the method needs more arguments I also need to write a custom filter tag.
3) Decide not to split...
Am I missing something and there's another option? When would you prefer (1) vs (2) ?
Note - I created a very simple example to keep the problem general.
You don't need to split anything, you have the pages, and comments have a foreign key to that so you can just iterate over the pages comments
{% for page in pages %}
{% for comment in page.comment_set.all %}
{% endfor}
{% endfor %}
If you want to be able to use the same template for a version of this page without comments you can just wrap the comment for loop in an {% if show_comments %} statement
I am trying to create a form in python / Flask that will add some dynamic slider inputs to a set of standard fields. I am struggling to get it to work properly, though.
Most of the web forms in my app are static, created through wtforms as in:
class CritiqueForm(Form):
rating = IntegerField('Rating')
comment = TextAreaField('Comments')
submit = SubmitField('Save Critique')
When I am explicit like that, I can get the expected results by using the CritiqueForm() in the view and passing the form object to render in the template.
However, I have a critique form that needs to dynamically include some sliders for rating criteria specific to a particular record. The number of sliders can vary form one record to the next, as will the text and IDs that come from the record's associated criteria.
When I looked for some ways to handle this, I found a possible solution from dezza (Dynamic forms from variable length elements: wtforms) by creating a class method in the form, which I could then call before instantiating the form I want to render. As in:
class CritiqueForm(Form):
rating = IntegerField('Rating')
comment = TextAreaField('Comments')
submit = SubmitField('Save Critique')
#classmethod
def append_slider(cls, name, label):
setattr(cls, name, IntegerField(label))
return cls
where 'append_slider' is always an IntegerField with a label I provide. This works enough to allow me to populate the criteria sliders in the view, as in:
#app.route('/critique/<url_id>/edit', methods=['GET', 'POST'])
def edit_critique(url_id):
from app.models import RecordModel
from app.models.forms import CritiqueForm
record = RecordModel.get_object_by_url_id(url_id)
if not record: abort(404)
# build editing form
ratings = list()
for i, criterium in enumerate(record.criteria):
CritiqueForm.append_slider('rating_' + str(i+1),criterium.name)
ratings.append('form.rating_' + str(i+1))
form = CritiqueForm(request.form)
# Process valid POST
if request.method=='POST' and form.validate():
# Process the submitted form and show updated read-only record
return render_template('critique.html')
# Display edit form
return render_template('edit_critique.html',
form=form,
ratings=ratings,
)
The ratings list is built to give the template an easy way to reference the dynamic fields:
{% for rating_field in ratings %}
{{ render_slider_field(rating_field, label_visible=True, default_value=0) }}
{% endfor %}
where render_slider_field is a macro to turn the IntegerField into a slider.
With form.rating—an integer field explicitly defined in CritiqueForm—there is no problem and the slider is generated with a label, as expected. With the dynamic integer fields, however, I cannot reference the label value in the integer field. The last part of the stack trace looks like:
File "/home/vagrant/msp/app/templates/edit_critique.html", line 41, in block "content"
{{ render_slider_field(rating_field, label_visible=True, default_value=0) }}
File "/home/vagrant/msp/app/templates/common/form_macros.html", line 49, in template
{% set label = kwargs.pop('label', field.label.text) %}
File "/home/vagrant/.virtualenvs/msp/lib/python2.7/site-packages/jinja2/environment.py", line 397, in getattr
return getattr(obj, attribute)
UndefinedError: 'str object' has no attribute 'label'
Through some debugging, I have confirmed that none of the expected field properties (e.g., name, short_name, id ...) are showing up. When the dust settles, I just want this:
CritiqueForm.append_slider('rating', 'Rating')
to be equivalent to this:
rating = IntegerField('Rating')
Is the setattr() technique inherently limiting in what information can be included in the form, or am I just initializing or referencing the field properties incorrectly?
EDIT:
Two changes allowed my immediate blockers to be removed.
1) I was improperly referencing the form field in the template. The field parameters (e.g., label) appeared where expected with this change:
{% for rating_field in ratings %}
{{ render_slider_field(form[rating_field], label_visible=True, default_value=0) }}
{% endfor %}
where I replace the string rating_field with form[rating_field].
2) To address the problem of dynamically changing a base class from the view, a new form class ThisForm() is created to extend my base CritiqueForm, and then the dynamic appending is done there:
class ThisForm(CritiqueForm):
pass
# build criteria form fields
ratings = list()
for i, criterium in enumerate(record.criteria):
setattr(ThisForm, 'rating_' + str(i+1), IntegerField(criterium.name))
ratings.append('rating_' + str(i+1))
form = ThisForm(request.form)
I don't know if this addresses the anticipated performance and data integrity problems noted in the comments, but it at least seems a step in the right direction.
setattr(obj, name, value) is the very exact equivalent of obj.name = value - both being syntactic sugar for obj.__setattr__(name, value) -, so your problem is not with "some limitation" of setattr() but first with how wtform.Form works. If you look at the source code, you can see there's much more to make fields and form work together than just having the fields declared as class attributes (metaclass magic involved...). IOW, you'll have to go thru the source code to find out how to dynamically add fields to a form.
Also, your code tries to set the new fields on the class itself. This is a big NO NO in a multiprocess / multithreaded / long-running process environnement with concurrent access - each request will modify the (shared at process level) form class, adding or overriding fields aphazardly. It might seem to work on a single-process single-threaded dev server with a single concurrent user but will break in production with the most unpredictable errors or (worse) wrong results.
So what you want to find out is really how to dynamically add fields to a form instance - or, as an alternative, how to dynamically build a new temporary form class (which is far from difficult really - remember that Python classes are objects too).