Django ComboField to change page content - django

I have a model item called project, where each project has a Title, Description and a date, which comprises of either Fall or Spring, and a year (eg. Fall 2012, or Spring 2013). I am able to successfully populate a page with all the Projects by using a simple for loop in my pages html. However, I want to have a ComboField with a list of dates (that I can specify) in the page so that when a user selects a date, only the Projects with that date will appear on the page.
What is the best way to accomplish this? Should I use the Django forms items or should I use a HTML select tag? If it is better to use a HTML tag, how would one go about accomplishing that?

Blindly coding it, do something like this in your forms.py:
from django import Forms
from .models import Project
class ProjectFilterForm(forms.Form):
range = forms.ChoiceField(choices=[], required=False)
def __init__(self, *args, **kwargs):
super(ProjectFilterForm, self).__init__(*args, **kwargs)
oldest = Project.objects.first()
newest = Project.objects.last()
if oldest and newest:
range_choices = []
for year in range(oldest.date.year, newest.date.year+1):
range_choices.append('Spring {}'.format(year))
range_choices.append('Fall {}'.format(year))
self.fields['range'].choices = range_choices
def get_filtered_projects(self):
if not self.is_valid():
return []
choice = self.cleaned_data['range']
parts = choice.split(' ')
month_range = [4,5,6,7,8,9] if parts[0] == 'Spring' else [10,11,12,1,2,3]
year = int(parts[1])
return Project.objects.filter(date__year=year, date__month__in=month_range)
Include the form as part of the template, and in the view use the get_filtered_projects to generate the list of projects to show in the rendered page.

Related

Django, exclude from main list, but viewable from django filter

I'm trying to add an archive section to my site, though when I say archive, I mean just not visible in the main list view that is the home page, the site is small and the projects are just small amounts of text so space and optimization are never going to be needed. I use django-filter to search through projects by department(Area in my code), and "Archive" is down as one of the departments. I've tried using exclude in the queryset, but of course that just removes any projects with area="Archive" completely so that the "Archive" section is empty. How can I get archived projects removed from the main list view but searchable via the filter.
View:
def project_list_view(request):
project_list = Project.objects.annotate(
display_date = models.Case(
models.When(models.Q(staff_trials__isnull=True) | models.Q(timeline_switch=False), then=models.F('launch')),
models.When(models.Q(staff_trials__isnull=False) & models.Q(timeline_switch=True), then=models.F('staff_trials')),
output_field=models.DateField(),
)
).exclude(area='1').order_by('-slip', 'display_date')
project_filter = ProjectFilter(request.GET, queryset=project_list)
return render(
request,
'project_portal/project_list.html',
{'filter': project_filter}
)
filter:
class ProjectFilter(django_filters.FilterSet):
class Meta:
model = Project
fields = ['area', 'user']
For clarity, Area has it's own Model with a foreign key to the Project model, and the departments are just entries in the database. The above code kind of does what I want, but as mentioned before makes archived projects not accessible in any way, whereas I want them to show if "Archive" is picked from the search dropdown.
One possible solution is add exclude to your queryset only if area filter not equal to archived:
def project_list_view(request):
project_list = Project.objects.annotate(
display_date = models.Case(
models.When(models.Q(staff_trials__isnull=True) | models.Q(timeline_switch=False), then=models.F('launch')),
models.When(models.Q(staff_trials__isnull=False) & models.Q(timeline_switch=True), then=models.F('staff_trials')),
output_field=models.DateField(),
)
).order_by('-slip', 'display_date')
if request.GET.get('area') != '1':
project_list = project_list.exclude(area='1')
project_filter = ProjectFilter(request.GET, queryset=project_list)
return render(
request,
'project_portal/project_list.html',
{'filter': project_filter}
)

Django list of objects doesn't render properly when using a form on the same page

I have developed a very simple Django app about music bands and their members (complete code: https://drive.google.com/file/d/1MwaC-_PchU6eBrwO01OaDivIF52S0L1D/view?usp=sharing). I used just the simple SQLlite3 database for my objects. I would appreciate help with rendering object lists when they need to be shown together with forms. They do render properly in templates that show just them, but not when a form is used on the same page with an object list.
What I want is to display, say, a list of bands from the database in the middle column. It shows them properly when I render just that (i.e. when I click the Bands link). However, when I click Add band in the right-hand column and hope the list to remain in the middle column, it suddenly shrinks as if there were no Band objects in the database.
In learning Django, I used the official Django tutorial, as well as the one provided by Mozilla (https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website), but could not figure out what I'm doing wrong.
The code below shows the relevant parts of the code. The link above takes you to the complete code (zipped), where the relevant HTML templates are included as well. I used Python 3.7, Django 2.2, and PyCharm 2019.2.
# Model:
class Band(models.Model):
rock = [
[False, 'no'],
[True, 'yes']
]
name = models.CharField(max_length=100, default='unknown')
country = models.CharField(max_length=100, default='USA')
is_rock = models.BooleanField(verbose_name="Rock 'n' roll",
choices=rock,
default=True)
def __str__(self):
return self.name + ', ' + self.country + (" (rock 'n' roll)" if self.is_rock else ' (other)')
def get_absolute_url(self):
"""Returns the URL to access a particular band.
Enables specific Festival pages in admin to include "View on site" button.
"""
return reverse('band-detail', args=[str(self.id)])
# View:
class BandListView(ListView):
model = Band
template_name = 'band/band_list.html'
paginate_by = 10
def get_queryset(self):
return Band.objects.all()
I suspect there is something wrong with the {% if band_list %} parts in the band_list.html and band_form.html templates I have included in the complete code linked above. I expected the list of bands to render the same way regardless of whether the form is rendered on the right side or not.
Any help and suggestion would be much appreciated. Thanks

How do I accept multiple choices for a Charfield from CheckboxSelectMultiple widget in Django?

I'm trying to build a form in Django 1.11, where I have a set of checkboxes on a watch list to allow a user to set multiple alerts on an item they want to receive notifications on later. However, I'm not sure how to represent multiple options on a field like this.
Here's my model code:
class Watchlist(models.Model):
CLOSES_IN_2_WEEKS, CLOSES_IN_1_WEEKS, CLOSES_IN_3_DAYS, CLOSES_TOMORROW = (
"CLOSES_IN_2_WEEKS",
"CLOSES_IN_1_WEEKS",
"CLOSES_IN_3_DAYS",
"CLOSES_TOMORROW"
)
ALERT_OPTIONS = (
(CLOSES_IN_2_WEEKS, "Closes in 2 weeks",),
(CLOSES_IN_1_WEEKS, "Closes in 1 weeks",),
(CLOSES_IN_3_DAYS, "Closes in 3 days",),
(CLOSES_TOMORROW, "Closes tomorrow",),
)
# I want to store more than one option
alert_options = models.CharField(max_length=255, blank=True)
def save(self):
"""
If this is submitted create 1 alert:
"CLOSES_IN_1_WEEKS"
If this submitted, create 3 alerts:
"CLOSES_IN_2_WEEKS",
"CLOSES_IN_1_WEEKS",
"CLOSES_IN_3_DAYS",
"""
# split the submitted text values, to create them
# yes, this should probably be a formset. I wasn't sure how I'd
# handle the logic of showing 4 optional alerts, and only creating models
# on the form submission, and remembering to delete them when a user
# unchecked the choices in the form below
And here's the form I'm using, below. I'm hooking into the __init__ method to pre-populate the form with possible choices.
class WatchlistForm(forms.ModelForm):
alert_options = forms.ChoiceField(
choices=[],
label="Alert Options",
required=False,
widget=forms.CheckboxSelectMultiple(),
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["alert_options"].choices = WatchlistForm.ALERT_OPTIONS
def clean_alert_options(self):
# I'm dropping into ipython to look around here
import ipdb; ipdb.set_trace()
return data["alert_options"]
class Meta:
model = WatchlistForm
fields = ("alert_options")
This currently works fine for a single checked checkbox, but once I have more than one, only the last choice is selected, and I can't quite figure out how to access it.
How can I capture all the choices here, rather than just the one?
I'm aware I probably should be using a formset here. The thing is it wasn't obvious to me how to create a set of pre-populated formset options to represent some active and some inactive alert choices.
Using a test to show what I'm going for
If it helps, I'm trying to save the information so it would be stored like so - I've added some pseudo code based on me using pytest in a test suite.
def test_form_accepts_multiple_alert_values(self, db, verified_user):
form_data = {
"user_id": verified_user.id,
"alert_options": "CLOSES_IN_2_WEEKS CLOSES_IN_3_DAYS",
}
submission = forms.WatchlistForm(form_data)
instance = submission.save()
assert instance.alert_options == "CLOSES_IN_2_WEEKS CLOSES_IN_3_DAYS",

update django choice field with database results

I am developing an application using django where the UI needs to be updated when user interacts with it. For instance I have a Drop down field where the user selects a drink and submits it then based on that a dropdown with the places that drink is available, price and quantity at each place needs to be displayed. The user will then further submit the form for second process.
From my understanding the Forms in django are pre-defined and I am not able to think of a way using which I could achieve this.
What I could come up was defining a regular form class
class dform(forms.Form):
SOURCES_CHOICES = (
(A, 'A'),
(E, 'E'),
)
drink = forms.ChoiceField(choices = SOURCES_CHOICES)
location = forms.ChoiceField(choices = **GET THIS FROM DATABASE**)
quantity = forms.ChoiceField(choices = **GET THIS FROM DATABASE**)
.
.
.
My view is like,
def getdrink():
if request.method == 'POST':
#code for handling form
drink = dform.cleaned_data['drink']
#code to get values from database
I have no idea how to generate or populate or append the values i get from the database to the choicefield in my form. I did try looking up on SO but none of the solutions here explained properly how to do it. Also, due to certain requirements I am not using the models. So my database is not at all related to the models.
I am at a total loss Please help me out
class MyForm(forms.Form):
my_choice_field = forms.ChoiceField(choices=MY_CHOICES)
So if you want the values to be dynamic(or dependent of some logic) you can simply modify your code to something like this:
either
def get_my_choices():
# you place some logic here
return choices_list
class MyForm(forms.Form):
my_choice_field = forms.ChoiceField(choices=get_my_choices())
or
User_list = [ #place logic here]
class MyForm(forms.Form):
my_choice_field = forms.ChoiceField(choices=get_my_choices())
but once database value is updated, new data value will be popoulated only on restart of server.
So write a function like this in forms:
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['my_choice_field'] = forms.ChoiceField( choices=get_my_choices() )
or in place of the get_my_choices u can ad the USER_LIST too.
If you have models for location and quantity, a ModelChoiceField should work:
class dform(forms.Form):
location = forms.ModelChoiceField(queryset = Location.objects.all())
Otherwise, you'll need to query the database directly, for example:
class dform(forms.Form):
location = forms.ChoiceField(choices = get_location_choices())
# elsewhere
from django.db import connection
def get_location_choices():
cursor = connection.cursor()
cursor.execute("select location_id, name from location_table")
return cursor.fetchall()
The SQL query to use here depends on your database engine and table schema.
I think that, based on my understanding of your question, the best solution would be to include JSON objects with your form and load these using jQuery instead of submitting the form over and over. Included in your form, you should add something like:
class MyForm(forms.Form):
CHOICE_DICT = {
'choice_1': [
'option_1',
'option_2',
],
etc...
Then you should include form.CHOICE_DICT in your context, load that with jQuery, and render it depending on changes to other fields.

django model search form

Firstly, I did my homework and looked around before posting! My question seems like a very basic thing that must’ve been covered before.
I'm now looking at Django-filter as a potential solution, but would like some advice on if this is the right way to go and if there any other solutions.
I have a Django app wit 10 models, each model has a few fields. Most fields are ChoiceField that users populate using forms with the default select widget. There is a separate form for each model.
I want to create a separate form for each model (in separate views) that users will use to search the database. The search form will contain only drop-down boxes (the select widgets) with the same choices as the forms used to populate the database with the addition of the “any” option.
I know how to use .object.filter(), however the “any” option would correspond to not include specific fields in the filter and I'm not sure how to add model fields to the filter based on users’ selection
I briefly looked at Haystack as an option but it seems to be made for full text search rather than “model filed search” I'm after.
Sample model (simplified):
class Property():
TYPE_CHOICES = (‘apartment’, ‘house’, ‘flat’)
type = charfield(choices=TYPE_CHOICES)
LOC_CHOICES = (‘Brussels’, ‘London’, ‘Dublin’, ‘Paris’)
location = charfield(choices=LOC_CHOICES)
price = PostivieInteger()
Users can select only “type”, only “location” or both (not making selection is equal to ANY) in which case I end up with 3 different filters:
Property.objects.filter(type=’apartment’)
Property.objects.filter(location=’Dublin’)
Property.objects.filter(type=’apartment’, location=’Dublin’)
The main question: django-filter the best option?
Question 1: what’s the best option of accomplishing this overall?
Question 2: how do I add model fields to the filter based on user’s form selection?
Question 3: how do I do the filter based on user selection? (I know how to use .filter(price_lt=).exclude(price_gt=) but again how do I do it dynamically based on selection as “ANY” would mean this is not included in the query)
I had a similar case like yours (real estate project), I ended up with the following approach, you can refine this to your needs...I removed select_related and prefetch_related models for easier reading
properties/forms.py:
class SearchPropertyForm(forms.Form):
property_type = forms.ModelChoiceField(label=_("Property Type"), queryset=HouseType.objects.all(),widget=forms.Select(attrs={'class':'form-control input-sm'}))
location = forms.ModelChoiceField(label=_('Location'), queryset=HouseLocation.objects.all(), widget=forms.Select(attrs={'class':'form-control input-sm'}))
Then in the properties/views.py
# Create a Mixin to inject the search form in our context
class SeachPropertyMixin(object):
def get_context_data(self, **kwargs):
context = super(SeachPropertyMixin, self).get_context_data(**kwargs)
context['search_property_form'] = SearchPropertyForm()
return context
In your actual view (I apply the search form as a sidebar element in my detailview only:
# Use Class Based views, saves you a great deal of repeating code...
class PropertyView(SeachPropertyMixin,DetailView):
template_name = 'properties/view.html'
context_object_name = 'house'
...
queryset = HouseModel.objects.select_related(...).prefetch_related(...).filter(flag_active=True, flag_status='a')
Finally your search result view (this is performed as GET request, since we are not altering any data in our DB, we stick to the GET method):
# Search results should return a ListView, here is how we implement it:
class PropertySearchResultView(ListView):
template_name = "properties/propertysearchresults.html"
context_object_name = 'houses'
paginate_by = 6
queryset = HouseModel.objects.select_related(...).prefetch_related(...).order_by('-sale_price').filter(flag_active=True, flag_status='a')
def get_queryset(self):
qs = super(PropertySearchResultView,self).get_queryset()
property_type = self.request.GET.get('property_type')
location = self.request.GET.get('location')
'''
Start Chaining the filters based on the input, this way if the user has not
selected a filter it wont be used.
'''
if property_type != '' and property_type is not None:
qs = qs.filter(housetype=property_type)
if location != '' and location is not None:
qs = qs.filter(location=location)
return qs
def get_context_data(self, **kwargs):
context = super(PropertySearchResultView, self).get_context_data()
'''
Add the current request to the context
'''
context['current_request'] = self.request.META['QUERY_STRING']
return context
Your solution works. I've modified it and I'm not using ModelChoiceField but the standard form.ChoiceField. The reason for that is that I wanted to add option "Any". My "if" statements look like:
if locality != 'Any Locality':
qs = qs.filter(locality=locality)
if property_type != 'Any Type':
qs = qs.filter(property_type=property_type)
if int(price_min) != 0:
qs = qs.filter(price__gte=price_min)
if int(price_max) != 0:
qs = qs.filter(price__lte=price_max)
if bedrooms != 'Any Number':
qs = qs.filter(bedrooms=bedrooms)
And so on....
This does the job, however it seems like an ugly and hacky solution to a simple problem. I would think is a common use case. I feel there should be a cleaner solution...
I've tried the django-filter. It is close to doing what I want but I couldn't add the "Any" choice and it filters inline rather than returning. It should do with some modifications.
Cheers