Django: get data from tables and display together using ListView - django

I want to display data from 2 tables (and more in the future), but something doesnt work in my code.
my views.py:
**imports**
def home(request):
context = {'users': Person.object.all(),
'emails': Email.object.all()
}
return render(request,'app/home.html',context)
class PersonListView(ListView):
model = Person
template_name = 'app/home.html'
context_object_name = 'users'
and in my home.html
{% extends "app/base.html" %}
{% block content %}
{% for user in users %}
Displaying user attributes works fine
{% endfor %}
Here should be emails
{% for email in emails %}
This displaying doesnt work
{% endfor %}
{% endbock content %}
So, displaying users works without any problem, but cant display anything form emails, but if I do it in shell, everything works well

A ListView [Django-doc] is designed to display only one queryset at a time. If you need to pass extra querysets, you can override the get_context_data(..) method [Django-doc]:
class PersonListView(ListView):
model = Person
template_name = 'app/home.html'
context_object_name = 'users'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(emails=Email.objects.all())
return context
Here we thus pass an extra variable emails to the template rendering engine. Note however that this queryset will not be paginated (or at least not without adding some pagination yourself).
my models.py:
Note that those are views, you need to write these in views.py.

Related

DRY approaches for displaying fields in a ListView with custom [exclude]

I am writing a generic template that I can use across all my models that require a ListView.
To do this, I know I can simply create a generic table in my template with a for loop over the object_list, but as each model is different I can't capture all the fields this way.
Instead I have created a (abstract) method that each model inherits, which produces a list of fields, names and values:
class MyModel(models.Model):
def get_display_fields(self, exclude_fields=[], adminonly_fields=[]):
"""Returns a list of all field names on the instance."""
fields = []
for f in self._meta.fields:
fname = f.name
# resolve picklists/choices, with get_xyz_display() function
get_choice = 'get_' + fname + '_display'
if hasattr(self, get_choice):
value = getattr(self, get_choice)()
else:
try:
value = getattr(self, fname)
except AttributeError:
value = None
if f.editable and f.name not in (exclude_fields or adminonly_fields):
fields.append(
{
'label': f.verbose_name,
'name': f.name,
'help_text': f.help_text,
'value': value,
}
)
return fields
I can then use this in my template which works universally across any model:
{% for obj in object_list %}
{% for f in obj.get_display_fields %}
<p>{{f.label}}</p>
<p>{{f.name}}</p>
<p>{{f.value}}</p>
{% endfor %}
{% endfor %}
Where I am stuck, is I want to allow some customisation of the exclude_fields and adminonly_fields in the view (which is on the model method). For example:
class MyGenericView(ListView):
exclude_fields = ['field1', 'field2']
adminonly_fields = ['field3',]
How can I pass these lists to get_display_fields?. I know I can just write them into the model method, but that defeats the point of this DRY approach. Can I append it to/modify the queryset somehow?
I don't want to use editable=False as I want to allow each view that subclasses MyGenericView to provide excluded_fields as an option.
Create a custom template tag that takes an argument. You will need to use the {% load %} tag to make it available.
It's important that you use a simple tag so that you can pass multiple arguments from your view.
from django import template
register = template.Library()
#register.simple_tag
def get_display_fields(obj, adminonly_fields=[], excluded_fields=[]):
if hasattr(obj, 'get_display_fields')
return obj.get_display_fields(adminonly_fields, excluded_fields)
return []
Pass adminonly_fields and excluded_fields as extra context data in your view so it can be used with your template tag.
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['adminonly_fields'] = self.adminonly_fields
context['excluded_fields'] = self.excluded_fields
return context
Then in your template.
{% for obj in object_list %}
{% get_display_fields obj adminonly_fields excluded_fields as display_fields %}
{% for f in display_fields %}
<p>{{f.label}}</p>
<p>{{f.name}}</p>
<p>{{f.value}}</p>
{% endfor %}
{% endfor %}

Beginners: Dynamic filtering of foreign key field in Django CreateView form

Models:
class Instructional_Cycle(models.Model):
date_started = models.DateField()
date_finished = models.DateField()
standard_tested = models.OneToOneField(Standard, on_delete=models.CASCADE)
class Standard(models.Model):
subject = models.CharField(max_length=14, choices=subjects)
grade_level = models.IntegerField(choices=gradeLevels)
descriptor = models.CharField(max_length=15)
description = models.TextField()
essential_status = models.BooleanField(default=False)
View:
class CycleCreateView(CreateView):
model = Instructional_Cycle
template_name = 'cycle_new.html'
fields = '__all__'
success_url = reverse_lazy('student_progress:cycles')
Template:
<!-- student_progress/cycle_new.html -->
{% extends 'base.html' %}
{% block content %}
<h1>Add a new instructional cycle:</h1>
<form action="{% url 'student_progress:cycle_new' %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<button name="submit">add cycle</button>
</form>
{% endblock content %}
The problem I'm having with this form is that the dropdown to select Instructional_Cycle.standard_tested has literally 1000 records from Standard. There's no way that the user can scroll through all of those and find the one record they want.
What I need is some way to click a link and filter the dropdown list by subject or grade_level and/or a search box, similar to what's achieved on the admin side by creating a custom admin model in admin.py like so:
class StandardAdmin(admin.ModelAdmin):
list_display = ('descriptor', 'description', 'essential_status')
list_filter = ('subject', 'grade_level', 'essential_status')
search_fields = ('descriptor',)
inlines = [MilestoneInLine]
def get_search_results(self, request, queryset, search_term):
queryset, use_distinct = super().get_search_results(request, queryset, search_term)
try:
search_term_as_int = int(search_term)
except ValueError:
pass
else:
queryset |= self.model.objects.filter(age=search_term_as_int)
return queryset, use_distinct
Please "dumb it down" for this newbie. I just finished working through Django for Beginners, and my conceptual model of how this all fits together is still full of holes. Please assume that I know hardly anything. Thanks!
That amount of reactive work on one page will require you to be comfortable with Javascript, Ajax, etc. If that is the case, there are a number of approaches you could take that let you refresh the form with the desired options.
Alternatively, you could ask the user for the necessary data one step earlier in the process and let Django build the correct form for you in the first place by overriding the form's default queryset.
You should look into using something like django-ajax-select. https://github.com/crucialfelix/django-ajax-selects

Pagination in Django TemplateView

I have a template where I want to display a large list of data, (received from external API rather than DB).
Although I'm aware this is easily done is ListView, however as I'm not pulling data from the database, TemplateView seems the best choice but what would the best way to display the list of data and paginate it?
Currently I have:
View
class QuotesResultsView(TemplateView):
template_name = 'site/quotes.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['quotes'] = self.request.session['quotes']['data']
return context
Template:
....
<div class="about">
{% for quote in quotes %}
<h3>Supplier:{{ quote.supplierName }}</h3>
<div>
<p>Annual Cost: {{ quote.newSpend }}</p>
<p>Savings: {{ quote.newSavings }}</p>
</div>
<button class="btn btn-cta-primary">Proceed</button>
{% endfor %}
</div><!--//about-->
You still can inherit ListView and override get_queryset() method, which must return an iterable. This will enable you to use pagination as usually
class QuotesResultsView(ListView):
template_name = 'site/quotes.html'
paginate_by = settings.QUOTES_PER_PAGE
context_object_name = 'quotes'
def get_queryset(self):
# Looks like your data is already an iterable
# if not convert it to iterable and return
return self.request.session['quotes']['data']
Don't forget to set QUOTES_PER_PAGE in your settings and import it in your views.py
Then in template your can use standard pagination snippet from docs.

How to overide django modelform to achieve custom behaviour

I have an Item object that has a manytomany relation to another object Option. I create a modelform from the Item object like so;
class Item(models.Model):
category = models.ForeignKey(Category)
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=9, decimal_places=2, blank=True, null=True)
options = models.ManyToManyField(Option)
class OptionForm(ModelForm):
options = forms.ChoiceField(widget=forms.RadioSelect())
class Meta:
model = Item
fields = ( 'options', )
When i render the form in the template it renders all available options for the Item object(the expected behavior) even those not created by a specific item. I want to be able to load options defined by the specific Item that will be chosen by the user. How do i override the form to achieve such behavior.for example without a form i can render an Items own Options through its id. item = Item.objects.get(pk=id)
Its tough to make ModelForm's defined on the fly because they are intimately tied to the structure in your model. Nevertheless you could use some clever template control flow and view rendering to get your desired effect. This is untested so you millage with this might vary.
<form method="post" action="">
{{ formset.management_form }}
{% for form in formset %}
{{ form.id }}
<ul>
{% if user_option.category %}
<li>{{ form.caregory }}</li>
{% endif %}
{% if user_option.name %}
<li>{{ form.name }}</li>
{% endif %}
{% if user_option.p_opt %}
<li>{{ form.price }}</li>
<li>{{ form.options }}</li>
{% endif %}
</ul>
{% endfor %}
</form>
From the Djano docs here.
Try overriding the form's init method and passing in the Item pk as an additional argument. The trick here is to pop the argument before calling the parent init.
class ItemOptionsForm(forms.ModelForm):
class Meta:
model = Item
def __init__(self, *args, **kwargs):
# pop 'item_id' as parent's init is not expecting it
item_id = kwargs.pop('item_id', None)
# now it's safe to call the parent init
super(ItemOptionsForm, self).__init__(*args, **kwargs)
# Limit options to only the item's options
if item_id:
try:
item = Item.objects.get(id=item_id)
except:
raise ValidationError('No item found!')
self.fields['options'] = forms.ChoiceField(item.options)
Then, in your view, create the form like:
form = ItemOptionsForm(item_id=item_id)
The nice thing about this is that you can raise ValidationErrors that will show up in the form.
Be aware that this doesn't prevent someone from POSTing option IDs to your form which do not belong to the Item, so you'll likely want to override the ModelForm.clean() to validate the options as well.
learning from link django: How to limit field choices in formset? provided by #jingo, i solved the problem by first of all creating dynamic form like so;
def partial_order_item_form(item):
"""dynamic form limiting optional_items to their items"""
class PartialOrderItemform(forms.Form):
quantity = forms.IntegerField(widget=forms.TextInput(attrs={'size':'2', 'class':'quantity','maxlength':'5'}))
option = forms.ModelChoiceField(queryset=OptionalItems.objects.filter(item=item),widget= forms.RadioSelect())
return PartialOrderItemform
then validating form like so;
def show_item(request,id):
option = get_object_or_404(Item,pk=id)
if request.method == 'POST':
form = partial_order_item_form(option)
#bound form to POST data,
final_form = form(request.POST)
# check validation of posted data
if final_form.is_valid():
order.add_to_order(request)
url =urlresolvers.reverse('order_index',kwargs={'id':a.id})
# redirect to order page
return HttpResponseRedirect(url)
else:
form = partial_order_item_form(item=id)
context={
'form':form,
}
return render_to_response('item.html',context,context_instance=RequestContext(request))

Django filter ModelFormSet field choices... different from limiting the Formset's queryset

I understand that it is possible to override the default queryset 'used' by the modelformset. This just limits the objects for which a form is created.
I also found a Stack Overflow question about filtering ForeignKey choices in a Django ModelForm, but not a ModelForm Set and about limiting available choices in a Django formset, but not a Model FormSet. I have included my version of this code below.
What I want to do is render a ModelFormSet, for a school class ('teachinggroup' or 'theclass' to avoid clashing with the 'class' keyword) with one field limited by a queryset. This is for a teacher's class-editing form, to be able to reassign pupils to a different class, but limited to classes within the same cohort.
My models.py
class YearGroup(models.Model):
intake_year = models.IntegerField(unique=True)
year_group = models.IntegerField(unique=True, default=7)
def __unicode__(self):
return u'%s (%s intake)' % (self.year_group, self.intake_year)
class Meta:
ordering = ['year_group']
class TeachingGroup(models.Model):
year = models.ForeignKey(YearGroup)
teachers = models.ManyToManyField(Teacher)
name = models.CharField(max_length=10)
targetlevel = models.IntegerField()
def __unicode__(self):
return u'Y%s %s' % (self.year.year_group, self.name)
class Meta:
ordering = ['year', 'name']
My views.py
def edit_pupils(request, teachinggroup):
theclass = TeachingGroup.objects.get(name__iexact = teachinggroup)
pupils = theclass.pupil_set.all()
PupilModelFormSet = modelformset_factory(Pupil)
classes_by_year = theclass.year.teachinggroup_set.all()
choices = [t for t in classes_by_year]
# choices = [t.name for t in classes_by_year] #### I also tried this
if request.method == 'POST':
formset = PupilModelFormSet(request.POST,queryset=pupils)
if formset.is_valid():
formset.save()
return redirect(display_class_list, teachinggroup = teachinggroup)
else:
formset = PupilModelFormSet(queryset=pupils)
for form in formset:
for field in form:
if 'Teaching group' == field.label:
field.choices = choices
return render_to_response('reassign_pupils.html', locals())
As you can see, I am limiting the choices to the queryset classes_by_year, which is only classes which belong to the same year group. This queryset comes out correctly, as you can see in the rendered page below, but it doesn't affect the form field at all.
My template
{% for form in formset %}
<tr>
{% for field in form.visible_fields %}
<td> {# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
<p><span class="bigtable">{{ field }}</span>
{% if field.errors %}
<p><div class="alert-message error">
{{field.errors|striptags}}</p>
</div>
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input type="submit" value="Submit changes"></p>
</form>
{{ choices }} <!-- included for debugging -->
The page renders with all teaching groups (classes) visible in the select widget, but the tag at the bottom of the page renders as: [<TeachingGroup: Y8 82Ma2>, <TeachingGroup: Y8 82Ma3>], accurately showing only the two classes in Year 8.
Note that I've also read through James Bennett's post So you want a dynamic form as recommended by How can I limit the available choices for a foreign key field in a django modelformset?, but that involves modifying the __init__ method in forms.py, and yet the only way I know how to create a ModelFormSet is with modelformset_factory, which doesn't involve defining any classes in forms.py.
Further to help from Luke Sneeringer, here is my new forms.py entry. After reading Why do I get an object is not iterable error? I realised that some of my problems came from giving a tuple to the field.choices method, when it was expecting a dictionary. I used the .queryset approach instead, and it works fine:
class PupilForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PupilForm, self).__init__(*args, **kwargs)
thepupil = self.instance
classes_by_year = thepupil.teaching_group.year.teachinggroup_set.all()
self.fields['teaching_group'].queryset = classes_by_year
class Meta:
model = Pupil
As best as I can tell, you've actually put all the pieces together except one. Here's the final link.
You said you read the dynamic form post, which involves overriding the __init__ method in a forms.Form subclass, which you don't have. But, nothing stops you from having one, and that's where you can override your choices.
Even though modelformset_factory doesn't require an explicit Form class (it constructs one from the model if none is provided), it can take one. Use the form keyword argument:
PupilModelFormset = modelformset_factory(Pupil, form=PupilForm)
Obviously, this requires defining the PupilForm class. I get the impression you already know how to do this, but it should be something like:
from django import forms
class PupilForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PupilForm, self).__init__(*args, **kwargs)
self.fields['teaching_group'].choices = ______ # code to generate choices here
class Meta:
model = Pupil
The last problem you might have is that a modelformset_factory just takes the class, which means that the constructor will be called with no arguments. If you need to send an argument dynamically, the way to do it is to make a metaclass that generates the form class itself, and call that metaclass in your modelformset_factory call.
You can accomplish this by setting field choices of a form in a formset is in the forms init and overwriting the self.fields['field_name'].choices. This worked fine for me but I needed more logic in my view after the formset was initialized. Here is what works for me in Django 1.6.5:
from django.forms.models import modelformset_factory
user_choices = [(1, 'something'), (2, 'something_else')] # some basic choices
PurchaserChoiceFormSet = modelformset_factory(PurchaserChoice, form=PurchaserChoiceForm, extra=5, max_num=5)
my_formset = PurchaserChoiceFormSet(self.request.POST or None, queryset=worksheet_choices)
# and now for the magical for loop and override each desired fields choices
for choice_form in my_formset:
choice_form.fields['model'].choices = user_choices
I wasn't able to find the answer for this but tried it out and it works in Django 1.6.5. I figured it out since formsets and for loops seem to go so well together :)