I'm trying to feed a select tag with options recovered from a database.
The problem is, I'm totally beginner with Django and don't even know how to search for this.
I'm using generic view and as far as I know, the template is fed by a model bound to a context_object, default named as object_list, but you can change it in the context_object_name variable.
But my companies_object is not feeding the template.
<tbody>
{% for project in projects %}
<tr>
<td>
{{ project.title }}
</td>
[...]
<select>
{% for company in companies %}
<option value="{{company.id}}">{{company.name}}</option>
{% endfor %}
</select>
class ProjectsView(LoginRequiredMixin, ListView):
model = Project
context_object_name = 'projects'
template_name = 'projects/projects.html'
def select_company(self):
companies = Company.objects.all()
return 1 #return selected company
def get_projects(self):
seek_in_database()
return projects
I expect to know how to show two different objects in the same template, the projects, which is already working, and the companies object.
I didn't figure it out yet how the template is getting the project's data, I suspect of model = Projects and context_object_name.
I know that it is begginer level, and I don't expect someone to write a complete guide, I'll be very happy with some instruction of what subject to look.
Here an example how I do it:
class CompanyListView(ListView):
model = Company
context_object_name = 'companies'
template_name = 'core/company/listCompanies.html'
queryset = Company.objects.get_main_companies()
def get_context_data(self, **kwargs):
context = super(CompanyListView, self).get_context_data(**kwargs)
context.update({
'company_abbr': self.request.session.get('company_abbr'),
'page_title': 'Manage Companies',
})
return context
So in get_context_data, you may add as many data as you need.
Related
Due to my lack of skills I think I overcomplicate and would like to get input for a simple solution. I'm creating kind of a web shop template. I have a service work order that I would like to add parts to. I have a decent looking template where I can list all parts that fits a machine model and then filter through them.
One Workorder many Parts (So I have one separate class for all parts objects for workorder). SO to avoid to have to update part number by part number, using create view. I have tried to use a listview where I try to select all parts at the same time.
In my CBV I define the filtering and get a context "Partslist" with all parts to show in table. In the template I have created an input fields qty. I.e. all part in the "Partslist" gets a "qty" inputfields.
In my post or save definition can I simply catch all input fields data with a self.request.GET statement somehow, or could I use the context "Partslist" to iterate through the fields?
models.py
class WorkOrderParts(models.Model):
wo_num = models.CharField(max_length=30, verbose_name='Order Number')
wo_pa_number = models.CharField(max_length=30, verbose_name='Part Number')
wo_pa_qty_def = models.DecimalField(max_digits=3,decimal_places=0,verbose_name='Qty Planned for')
class Parts(models.Model):
pa_number = models.CharField(max_length=30,verbose_name='Part Number')
pa_group1 = models.CharField(max_length=30,verbose_name='Group of Parts')
pa_group2 = models.CharField(max_length=30,verbose_name='2:nd Group of Parts')
pa_fits = ArrayField(models.CharField(max_length=20,verbose_name='models that part fit to (BRAND-MODEL)'),default=list, null=True, blank=True)
views.py
class WO_PartCreate(LoginRequiredMixin,ListView):
login_url = '/login'
template_name = 'AddPart.html'
model = WorkOrderParts
def get_context_data(self, **kwargs):
model = 'my machine model'
qs=Parts.objects.filter(pa_fits__icontains=model)
context = super().get_context_data(**kwargs)
context['Partslist'] = qs
return context
def post(self):
??? This is where I want to take all parts with input qty>0 and update new objects into WorkOrderParts.
AddPart.html
<table>
<thead>
<tr>
<th class="text-left">Part Number</th>
<th class="text-left">Quantity</th>
</tr>
</thead>
{% for item in Partslist %}
<tbody>
<tr>
<td class="text-left">
{% autoescape off %}{{item.pa_number}}{% endautoescape %}<br>
</td>
<td class="text-left">
<input type="number" method="GET" name="{{ item.pa_number }}-Qty"/>
</td>
</tr>
</tbody>
I hope I have been able to explain enough for someone to understand me.The reason why I use a table is simply that I have much more data in there that I want to display.
OK I know there is most probably better ways to solve this. But since I at least was able to find out something that worked, I thought I would share it with others.
It at least solved my immediate need to grab input fields from a dynamic list and do something with it.
views.py
class AddPartTest(LoginRequiredMixin,ListView):
login_url = '/login'
template_name = 'AddPart.html'
model = Parts
form_class = Add_Parts2 ##including selected fields
def get_context_data(self, **kwargs):
order = self.kwargs.get('str')
wo = WorkOrder_main.objects.get(order=order)
model = wo.wo_machine_model
qs=Parts.objects.filter(pa_fits__icontains=model)
first_contains_query = self.request.GET.get('pa_group1')
if is_valid_queryparam(first_contains_query):
qs=qs.filter(pa_group1=first_contains_query)
second_contains_query = self.request.GET.get('pa_group2')
if is_valid_queryparam(second_contains_query):
qs=qs.filter(pa_group2= second_contains_query)
context = super().get_context_data(**kwargs)
context['Partslist'] = qs
return context
def post (self, request, *args, **kwargs):
order = self.kwargs.get('str')
context = self.get_context_data()
Partslist_x = context['Partslist']
Parts_num_list = Partslist_x.values_list('pa_number', flat=True)
for part in Parts_num_list:
qty = part + 'Qty'
Qty = self.request.POST.get(qty)
if int(Qty)==0:
pass
else:
WorkOrderParts.objects.create(
wo_num=order,
wo_pa_number=part,
wo_pa_qty_def=Qty,
)
path='where I want to end up'
return redirect (path)
I have a simple m2m relationship as below:
class Category(ModelBase):
name = models.CharField(max_length=255)
icon = models.CharField(max_length=50)
class Course(ModelBase):
name = models.CharField(max_length=255, unique=True)
categories = models.ManyToManyField(Category, related_name="courses")
I am using ListView to show all the courses in a category or all courses if no category provided.
views.py
class CourseListView(ListView):
model = Course
paginate_by = 15
template_name = "courses.html"
context_object_name = "courses"
def get_queryset(self):
queryset = (
super()
.get_queryset()
.select_related("tutor")
.prefetch_related("categories")
.filter(active=True)
)
category_id = self.kwargs.get("category_id")
return (
queryset
if not category_id
else queryset.filter(categories__in=[category_id])
)
def get_context_data(self, *args, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
category_id = self.kwargs.get("category_id")
if category_id:
context["current_category"] = Category.objects.get(id=category_id)
context["categories"] = Category.objects.all()
return context
Django is making duplicate calls as I am doing something like this in the template.
<div class="icon"><span class="{{ course.categories.first.icon }}"></span></div>
Not sure why, help much appreciated. Thanks!
When you do .prefetch_related('categories') the result of this prefetch will be used when you access course.categories.all. Any other queryset on course.categories will do a fresh query. Since course.categories.first is a new queryset, it does not use the prefetched result.
What you want to access in your template is the first result from course.categories.all(). But this is not easy in the template. I would recommend a method on the Course model:
class Course(...):
...
def first_category(self):
# Equivalent to self.categories.first(), but uses the prefetched categories
categories = self.categories.all()
if len(categories):
return categories[0]
else:
return None
And then in your template you can call this method
<div class="icon"><span class="{{ course.first_category.icon }}"></span></div>
You can also access the first value like:
{{ course.categories.all.0.icon }}
It is not necessary to write a method.
because categories is ManyToMany which means one category may appear in many courses, but in the template you just calling the first category's icon, so there maybe more than two course with the same first category, and it will retrieve them all, i recommend using another for loop to loops through categories too.
{% for course in courses %}
<div>
<h1>{{ course.name</h1>
......
<h4>categories</h4>
{% for category in course.categories %}
<div class="icon"><span class="{{ category.icon }}"></span></div>
{% endfor %}
</div>
{% endfor %}
I have employed django simple history package on the admin site to be able to track and revert to previous versions of the object of the model. I am designing a web form that allows users to change instances of the model object using model form on django and would like to allow the users to view and revert to previous versions. Also to allow them to see what are the changes compared to the current version.
With the code below I am able to get the list of historical records on my template under histoire.
class CompanyDetailView(LoginRequiredMixin,generic.DetailView):
model = Company
def get_context_data(self, **kwargs):
context = super(CompanyDetailView, self).get_context_data(**kwargs)
company_instance = self.object
context['histoire'] = company_instance.history.all()
return context
In my template,
<p>
Previous versions:
{% for item in histoire %}
<li>
{{ item }} submitted by {{ item.history_user }} {{
item.history_object }}
</li>
{% endfor %}
</p>
But ideally I want item.history_object to be a link that users can view the previous object and be able to revert if desired.
I did something similar by adding HistoricForm to my model forms.
class MyModelForm(HistoricForm, ModelForm):
...
HistoricForm takes and extra history_id kwarg.
If history_id is provided HistoricForm swaps the ModelForm instance with the historic_instance (what your instance looked like at the time of history_id). This way your form will show the historic version of your object.
class HistoricForm(object):
def __init__(self, *args, **kwargs):
self.history_id = kwargs.pop('history_id', None)
instance = kwargs.get('instance')
if instance and self.history_id:
kwargs['instance'] = self.get_historic_instance(instance)
super(HistoricForm, self).__init__(*args, **kwargs)
def get_historic_instance(self, instance):
model = self._meta.model
queryset = getattr(model, 'history').model.objects
historic_instance = queryset.get(**{
model._meta.pk.attname: instance.pk,
'history_id': self.history_id,
}).instance
return historic_instance
If history_id is not provided the ModelForm works as usual.
You can revert by showing the historic instance and save (this way you will post your historic data).
I am using django-extra-views in order to have sortable tables in my Django ListViews.
I'm not 100% sure of why I can't get it working, but I've always found working from tests.py difficult wrt templates.
So I have this in my views.py
class PartTypePartList(SortableListMixin, generic.ListView):
model = PartNumber
template_name = 'inventory/newparttype_list.html'
sort_fields = ['name',]
paginate_by = 25
def get_queryset(self):
self.parttype = self.kwargs['parttype']
return PartNumber.objects.filter(fds_part_type=self.parttype)
def get_context_data(self, **kwargs):
context = super(PartTypePartList, self).get_context_data(**kwargs)
context['parttype'] = self.parttype
return context
And in urls.py
url(r'^newparttype/(?P<parttype>\d{2})/$', views.PartTypePartList.as_view(), name='new_part_type_view'),
And with these two we are getting the list as expected.
In the relevant template:
Name
asc name
desc name
{% if sort_helper.is_sorted_by_name %} ordered by name {{ sort_helper.is_sorted_by_name }} {% endif %}
The issue is that there is no sorting happening. In particular,
{{ sort_helper.get_sort_query_by_name }} and
{{ sort_helper.get_sort_query_by_name_asc }} and
{{ sort_helper.get_sort_query_by_name_desc }}
each return an empty string.
What am I doing wrong?
I was using django-tables2 but the owner admitted he would not be continuing dev on it and I'm not skilled enough or time rich enough to take it on myself.
[EDIT]
I believe this still deserves a solution, but I've re-written the view to be a FBV rather than a CBV and am manipulating the data accordingly
[/EDIT]
You need to call get_queryset parent method:
def get_queryset(self):
self.parttype = self.kwargs['parttype']
qs = super(PartTypePartList, self).get_queryset()
qs = qs.filter(fds_part_type=self.parttype)
return qs
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 :)