Pass other object/data into Flask Admin model view edit template - flask

I'm extending the edit template for a ModelView so that I can show some other information from the database that is relevant for determining how to edit the record in this view. I know how to extend the template and get it to work, but I can't figure out how to query an object and use it in the template.
Also I need to use the value from the model/record in querying the new object I need to pass.
Here is my code from init.py:
class MilestoneView(ModelView):
edit_template = '/admin/milestone_model/milestone_edit.html'
can_delete = True
#i need something like this to work:
referrals = Referral.query.filter_by(email=model.email)
#then i need to pass referrals into the template
admin = Admin(app, name="My App", template_mode='bootstrap3')
admin.add_view(MilestoneView(Milestone, db.session, name='Milestones'))
Then from milestone_edit.html, I want something like this to work:
{% extends 'admin/model/edit.html' %}
{% block body %}
{{ super() }}
{% for r in referrals %}
<p>{{ r.name }}</p>
{% endif %}
{% endblock %}
But of course the referrals object is not available to use in the template. How do I customize this ModelView in order to pass this object in from the init file? I've reviewed the available posts on this subject(ish) on here and haven't found an answer. Thanks in advance.

Override your view's render method, see code on Github, and test if the view being rendered is the edit view. Now you can inject any data into the kwargs parameter. For example:
class MilestoneView(ModelView):
def render(self, template, **kwargs):
# we are only interested in the edit page
if template == 'admin/model/milestone_edit.html':
# Get the model, this is just the first few lines of edit_view method
return_url = get_redirect_target() or self.get_url('.index_view')
if not self.can_edit:
return redirect(return_url)
id = get_mdict_item_or_list(request.args, 'id')
if id is None:
return redirect(return_url)
model = self.get_one(id)
if model is None:
flash(gettext('Record does not exist.'), 'error')
return redirect(return_url)
referrals = Referral.query.filter_by(email=model.email)
kwargs['referrals'] = referrals
return super(MilestoneView, self).render(template, **kwargs)
Note how the model is retrieved. This is a direct copy of the code in method edit_view code. Adjust the code for your use-case.
Use the variable referrals in your edit Jinja2 template.
The render method is called in the following routes for each view:
'/' - i.e. the list view code
'/new/' - code
'/edit/' - code
'/details/' - code

Related

Django - Open create/add form with fields precompiled based on other object model

In my project I use Django admin with Django 1.11, so I have a modelA and a modelB and users can create both of them, but sometimes I need to create a modelA object with some values field based on modelB object.
To do this I have override my /templates/my_app_name/my_modelB/change_form.html template and add a button like this:
{% extends "admin/change_form.html" %}
{% load i18n admin_urls %}
{% block object-tools-items %}
<div class="form-row form-row-field-quick-view">
Create ObjA from this ObjB
</div>
{% endif %}
{% endblock %}
In this way when I open some ObjB change form I get a link, based on a view whose task is redirect users to the URL /admin/app_name/modelA/add/ and show the proper form with some fields precompiled based on ObjB values which the form came from.
So I created a view like this:
class create_object_A_view(View):
model = ModelA
url = '/admin/app_name/modelA/add/'
def get(self, request, *args, **kwargs):
#get data from objB, say it foo and bar....
form = ModelAForm(initial={'field1': 'foo' , 'field2':'bar'})
return redirect(self.url,
{'form':form,
'opts': self.model._meta,
'change': False,
'is_popup':False,
'save_as':False,
'has_delete_permission':False,
'has_add_permission': False,
'has_change_permission': False})
Doing this I get the correct redirect to /admin/app_name/modelA/add/ but the fields that I want to be precompiled are empty.
Is this the proper way to achieve this result?
You can make that happen, by redirecting to the modelA add url with the form data in url itself
for example:
return redirect(self.url+"?"+form.data.urlencode())

Update non-request user information in a Django template and view

So I have a ManageUserForm in forms.py-- it renders correctly but it doesn't pull the right data from the user i'm trying to edit.
In the template, I have a for loop that works correctly
{% for tenants in tenants %}
{{ tenants.user }} {{ tenants.type }}
{% endfor %}
This template renders the list of objects in the UserProfile. And it does it correctly. The challenge I face is updating the "tenants.type" attribute. Again, the type shows up correctly but I don't know how to update it from this template page.
#views.py
def manage_users(request):
tenants = UserProfile.objects.all()
form = ManageUserForm(request.POST or None)
if form.is_valid():
update = form.save(commit=False)
update.save()
return render_to_response('manage_users.html', locals(), context_instance=RequestContext(request))
#forms.py
class ManageUserForm(forms.ModelForm):
class Meta:
model = UserProfile
exclude = ('full_name', 'user',)
`I think I need to call an instance but I have no idea how to do so for the non-request users AND still follow the pattern for the template. The template basically is a list of users where the request user (staff user) will be able to change the data in the list.
Thank you for your help!
You have one form for one user. You need a FormSet if you want to use that form to edit multiple tenants. Editing objects and displaying them are entirely different beasts; dont' confuse them.
formset = modelformset_factory(form=ManageUserForm, queryset=tenants)
Update:
You should have one {{ form.management_form }} and the rest of the {% for form in formset %}{{ form }}{% endfor %} in one <form> tag. All of your forms are the first form in the formset.
You should rewrite your template loop to iterate through formset forms instead of tenant objects. The tenant object can be accessed through {{ form.instance }}
Update 2:
You have an extra form because you probably haven't passed in the extra=0 parameter to the modelformset_factory function. These forms are typically used to add/edit data; thus it has support for adding N blank forms for creating.

Django: How to pass object/object id to another template

I am new to the Django web framework.
I have a template that displays the list of all objects. I have all the individual objects listed as a link (object title), clicking on which I want to redirect to another page that shows the object details for that particular object.
I am able to list the objects but not able to forward the object/object id to the next template to display the details.
views.py
def list(request):
listings = listing.objects.all()
return render_to_response('/../templates/listings.html',{'listings':listings})
def detail(request, id):
#listing = listing.objects.filter(owner__vinumber__exact=vinumber)
return render_to_response('/../templates/listing_detail.html')
and templates as:
list.html
{% for listing in object_list %}
<!--<li> {{ listing.title }} </li>-->
{{ listing.title}}<br>
{% endfor %}
detail.html
{{ id }}
The variables that you pass in the dictionary of render_to_response are the variables that end up in the template. So in detail, you need to add something like {'listing': MyModel.objects.get(id=vinumber)}, and then the template should say {{ listing.id }}. But hat'll crash if the ID doesn't exist, so it's better to use get_object_or_404.
Also, your template loops over object_list but the view passes in listings -- one of those must be different than what you said if it's currently working....
Also, you should be using the {% url %} tag and/or get_absolute_url on your models: rather than directly saying href="{{ listing.id }}", say something like href="{% url listing-details listing.id %}", where listing-details is the name of the view in urls.py. Better yet is to add a get_absolute_url function to your model with the permalink decorator; then you can just say href="{{ listing.get_absolute_url }}", which makes it easier to change your URL structure to look nicer or use some attribute other than the database id in it.
You should check the #permalink decorator. It enables you to give your models generated links based on your urls pattern and corresponding view_function.
For example:
# example model
class Example(models.Model):
name = models.CharField("Name", max_length=255, unique=True)
#more model fields here
#the permalink decorator with get_absolute_url function
#models.permalink
def get_absolute_url(self):
return ('example_view', (), {'example_name': self.name})
#example view
def example_view(request, name, template_name):
example = get_object_or_404(Example, name=name)
return render_to_response(template_name, locals(),
context_instance=RequestContext(request))
#example urls config
url(r'^(?P<name>[-\w]+)/$', 'example_view', {'template_name': 'example.html'}, 'example_view')
Now you can do in your templates something like this:
<a href={{ example.get_absolute_url }}>{{ example.name }}</a>
Hope this helps.
In your detail method, just pass the listing into your template like so:
def detail(request, id):
l = listing.objects.get(pk=id)
return render_to_response('/../templates/listing_detail.html', {'listing':l})

if..else custom template tag

I'm implementing a custom permissions application in my Django project, and I'm lost as to how to implement a custom template tag that checks a logged in user's permissions for a specific object instance and shows a piece of HTML based on the outcome of the check.
What I have now is (pseudocode):
{% check_permission request.user "can_edit" on article %}
<form>...</form>
{% endcheck %}
('check_permission' is my custom template tag).
The templatetag takes in the user, the permission and the object instance and returns the enclosed HTML (the form). This currently works fine.
What I would like to do however, is something like:
{% if check_permission request.user "can_edit" on article %}
<form>...</form>
{% else %}
{{ article }}
{% endif %}
I've read about the assignment tag, but my fear is that I would pollute the context variable space with this (meaning I might overwrite previous permission context variables). In other words, as the context variables are being defined on different levels (the view, middleware in my case, and now this assignment template tag), I'm worried about maintainability.
You can use template filters inside if statements. So you could rewrite your tag as a filter:
{% if request.user|check_can_edit:article %}
Note that it's tricky to pass multiple arguments of different types to a filter, so you'll probably want to use one filter per permission, above I've used check_can_edit.
You can definitely do that if you're willing to write some more lines of python code to improve your template readability! :)
You need to parse the tag content yourself, even the parameters it takes and then resolve them, if you want to use variables on them.
The tag implemented below can be used like this:
{% load mytag %}
{% mytag True %}Hi{% else %}Hey{% endmytag %} Bro
Or with a variable:
{% mytag myobject.myflag %}Hi{% else %}Hey{% endmytag %} Bro
So, here's the way I did it:
from django.template import Library, Node, TemplateSyntaxError
register = Library()
#register.tag
def mytag(parser, token):
# Separating the tag name from the "test" parameter.
try:
tag, test = token.contents.split()
except (ValueError, TypeError):
raise TemplateSyntaxError(
"'%s' tag takes two parameters" % tag)
default_states = ['mytag', 'else']
end_tag = 'endmytag'
# Place to store the states and their values
states = {}
# Let's iterate over our context and find our tokens
while token.contents != end_tag:
current = token.contents
states[current.split()[0]] = parser.parse(default_states + [end_tag])
token = parser.next_token()
test_var = parser.compile_filter(test)
return MyNode(states, test_var)
class MyNode(Node):
def __init__(self, states, test_var):
self.states = states
self.test_var = test_var
def render(self, context):
# Resolving variables passed by the user
test_var = self.test_name.resolve(context, True)
# Rendering the right state. You can add a function call, use a
# library or whatever here to decide if the value is true or false.
is_true = bool(test_var)
return self.states[is_true and 'myvar' or 'else'].render(context)
And that's it. HTH.
In Django 2 the assignment tag was replaced by simple_tag() but you could store the custom tag result as a template variable:
# I'm assuming that check_permission receives user and article,
# checks if the user can edit the article and return True or False
{% check_permission user article as permission_cleared %}
{% if permission_cleared %}
<form>...</form>
{% else %}
{{ article }}
{% endif %}
Check the current doc about custom template tags: https://docs.djangoproject.com/en/2.1/howto/custom-template-tags/#simple-tags
inside my_tags.py
from django import template
register = template.Library()
#register.simple_tag(takes_context=True)
def make_my_variable_true(context):
context['my_variable'] = True
return '' # without this you'll get a "None" in your html
inside my_template.html
{% load my_tags %}
{% make_my_variable_true %}
{% if my_variable %}foo{% endif %}
In this case best solution is to use custom filter. If you don't want write long code for custom tag. Also if you don't want to copy/paste others code.
Here is an example
Inside templatetag
register = template.Library()
def exam_available(user, skill):
skill = get_object_or_404(Skill, id=skill)
return skill.exam_available(user)
register.filter('exam_available', exam_available)
Inside template
{{ request.user|exam:skill.id }}
or
{% if request.user|exam:skill.id %}
Since one of the main common of it is to use request.user or any specific object(id) inside model's custom method, so filtering that individual object or user is the easiest way to make it done. :)

Generic templates or templates in models - Django

What is the best approach in a following situation?
Say we have some models, e.g. Article, Photo, BlogEntry and so on. Every model can be displayed on the page as a thumb or a so-called widget.
Example:
attribute thumbview of a model contains item thumb with title in html block
normalview - contains larger thumb, title and description in a block
bigview - thumb, title, description and say... number of comments addded
All of these should be in some way polymorphic in a template so I could do something like iterating through my list of abstract items (various types) and simply:
{{ item.thumbview }}
or
{{ item.bigview }}
to display every item thumb.
It can be achieved lazy-evaluated in a model but I don't feel hardcode'ing html in a model is the right way.
How can I model such a behaviour? What is the best way?
I would appreciate any suggestion. Thanks.
You can use a custom template tag and a standard method in a model to give a context to the widget in case you can't achieve some properties in a template:
myapp/models.py:
class Photo(models.Model):
...
def widget_context(self, context): # receives the context of the template.
user = context['request'].user # context should be RequestContext to contain request object (or you may use thread locals).
return {'tags': self.tag_set.filter(...), 'can_edit': self.owner == user or user.is_admin}
template tags file, widgets_app/templatetags/objwidgets.py:
#register.simple_tag(takes_context=True)
def object_widget(context, obj, size='small'):
context_func = getattr(obj, 'widget_context') # try getting the context method
extra_context = context_func(context) if context_func else {}
extra_context['obj'] = obj
long_tuple = (obj._meta.app_label, 'widgets', obj.__class__.__name__, size)
return render_to_string([ # multiple templates to have both generic and specific templates
'%s/%s/%s.%s.html' % long_tuple, # the most specific (photos/widgets/photo.small.html)
'%s/widget.%s.%s.html' % (obj._meta.app_label, obj.__class__.__name__, size),
'%s/widget.%s.html' % (obj._meta.app_label, size), # myapp/widget.small.html
'widget.%s.html' % size,
],
extra_context
context)
usage:
{% load objwidgets %}
{% object_widget photo1 'large' %}
{% object_widget photo2 %}
make a template for the object widget, myapp/widgets/photo.small.html:
<b>{{ obj.name }}</b>
<img src="{{ obj.thumbnail.url }}"/>
{% if can_edit %}edit{% endif %}
{% for t in tags %}
{{ tag.text }}
{% endif %}
You shouldn't generate html at all in your model. You can write some custom template tags to achieve what you need. If you are using django dev version, you can create an inclusion tag with an argument that could return a piece of html depending on the model type of the input.