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).
Related
I have two models, Task and TaskNote, and I am trying to get the current Task's primary key entered as an initial value in the TaskNoteForm when the form is called from the current Task's view.
On the task detail view there is a link to a form where the user can write a note that will appear on the detail view. I would like the form to have the Task instance primary key set as a foreign key in TaskNote (for example, as model attribute "subject").
My solution was to get the URL using a 'HTTP_REFERER' request and parse the pk from the URL, then pass the pk into context data in the view, and finally enter it as a value in template. For example:
# Models
class Task(models.Model):
id = models.AutoField(
primary_key=True)
...
class TaskNote (models.Model):
...
subject = models.ForeignKey(
Task,
on_delete=models.CASCADE)
...
# view
def get_url_pk(self):
url = self.request.META.get('HTTP_REFERER')
t = url.split('/')
pk = int(t[-1])
return pk
class TaskNotesCreate(CreateView):
template_name='tasknotes_new.html'
form_class = TaskNoteForm
model = TaskNote
def get_context_data(self, **kwargs):
context = super(TaskNotesCreate, self).get_context_data(**kwargs)
context['pk'] = get_url_pk(self)
return context
def get_success_url(self, **kwargs):
obj = self.object
return reverse('task_view', kwargs={'pk': obj.subject_id})
# template
...
<div class="form-group">
<label for="exampleInput" class="required">
<input type="hidden" name="subject" value="{{ pk }}">
</div>
This works fine, however...
I have learned that HTTP_REFERER is not the preferred method as it can be disabled in some browsers. I can't seem to find the what the preferred method is in the docs, so if anyone could help me on that, that would be great. But also, before I spend a lot to time hacking something else together, I am wondering...
Is there a better, more acceptable way to pass the pk to the form???
Thanks
Rather than editing the template, you can do it the following way:
Update your TaskNoteForm so it has hidden input field for the value you want to pass along (this is a normal form field with widget=HiddenInput()).
On the view, overwrite get_initial to pass the object's value as the pk. This should look like
def get_initial():
initial = super().get_initial()
initial.update(
{"pk": self.object.pk}
)
return initial
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.
I'm using the django-celery-results extension and successfully saving records in the db backend table, celery_results_taskresults. My tasks are associated with model instances, and I want to be able to list them as attributes of each instance, in views and ultimately templates. I can see them in the admin interface, but can't figure out how to access them in a list.
I though of creating an #property on the model in question, using raw sql, but the sql examples I've seen all refer to a model, and if there's a celery_results_taskresults model, I can't find it.
As celery_results_taskresults use a model to store results, so we can use them in the views. You can try like this:
from django_celery_results.models import TaskResult
class SomeTemplateView(TemplateView):
def get_context_data(self, *args, **kwargs):
context = super(SomeTemplateView, self).get_context_data(*args, **kwargs)
context['results'] = TaskResult.objects.all()
return context
And in the template:
{% for r in results %}
{{r.task_name}}
...
{% endfor %}
I have Django template which has one CharField. Users can enter a mobile phone number here and submit it.
The view connected to this template sometimes passes to the said template, a context variable containing a pre-defined phone number {{ number }}.
How do I auto-populate the CharField in the Django template with {{ number }}?
The template code is like so:
<form action="{% url 'user_phonenumber' slug=unique num=decision %}" method="POST">
{% csrf_token %}
<b style="font-size:90%;color:green;">Enter your mobile number:</b><br><br>
{{ form.mobile_number }}<br><br>
<input type="submit" name="number" value="OK"><br>
</form>
If required, forms.py contains the following:
class UserPhoneNumberForm(forms.Form):
mobile_number = forms.CharField(max_length=50)
class Meta:
fields = ("mobile_number")
In views.py, I simply have:
class UserPhoneNumberView(FormView):
form_class = UserPhoneNumberForm
template_name = "get_user_phonenumber.html"
def get_context_data(self, **kwargs):
context = super(UserPhoneNumberView, self).get_context_data(**kwargs)
if self.request.user.is_authenticated():
#some code to determine what number should be
context["number"] = number
return context
I've looked at a few other SO answers. The last one I've linked comes close to solving the problem, but the solution isn't for dynamic values (which is what I need), plus that's for modelforms. The rest don't quite address my particular needs.
I needed to over-ride def get_initial(self): of my FormView to achieve this. I.e. instead of passing a context variable to the template, pass the variable as initial through get_initial() to the related form.
Here's how I did it:
def get_initial(self):
"""
Returns the initial data to use for forms on this view.
"""
if self.request.user.is_authenticated():
#What's the latest chatpicmessage which houses a pic you own
try:
msg = PicMessage.objects.filter(which_pic__owner=self.request.user).latest('sending_time')
self.initial = {'mobile_number': msg.what_number} #initial needs to be passed a dictionary
return self.initial
except:
return self.initial
else:#i.e user is not authenticated
return self.initial
return self.initial
More documentation regarding this can be found here: https://docs.djangoproject.com/es/1.9/ref/forms/fields/#initial
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 :)