Django - adding extra html in admin - python

I have a model class like this
models.py
class Places(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=50)
tel = models.CharField(max_length=50)
I want to put a google map under the address field and have a custom button next to the address field. The button is used to pin point the location when user click on it, based on the address input.
So, my question is how do I insert the button next to the address fields and html tags under it for map rendering?
Thanks.

You'll have to write a custom widget.
Take a look at this, this and django documentation.
You'll probably need to write south introspection rules

There is a django-easy-maps module that can do exactly what you need (besides other features):
django-easy-maps provides basic widget that displays a map under the
address field. It can be used in admin for map previews.
Note that you would not need to change your model and write any specific migrations. It works with either CharField, or TextField out of the box.
Follow the installation instructions and add the following to the admin.py of your app:
from django import forms
from django.contrib import admin
from easy_maps.widgets import AddressWithMapWidget
from my_app.models import Places
class PlacesAdmin(admin.ModelAdmin):
class form(forms.ModelForm):
class Meta:
widgets = {
'address': AddressWithMapWidget({'class': 'vTextField'})
}
admin.site.register(Places, PlacesAdmin)

I end up refer to this tutorial to create a custom widget.
For anyone who are interested, here are my codes
widgets.py
class AddressFieldWidget(forms.TextInput):
def render(self, name, value, attrs=None):
html = super(AddressFieldWidget, self).render(name, value,
attrs)
html = html + """ <input type="button" value="GeoCode" class="getgeo btn"><br><br><label>Map</label><div id="gmap">This is for map rendering</div>"""
return mark_safe(html)
admin.py
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name == 'address':
kwargs['widget'] = AddressFieldWidget
return super(AdminListing,self).formfield_for_dbfield(db_field,**kwargs)

Related

Creating a display-only (non-editable) Django admin field

Is it possible to build a custom model field/widget combination which displays a value but never writes anything back to the database? I would use this widget exclusively in the admin's forms.
I wrote my own field, which overwrites the formfield() method to declare its own widget class. It displays just fine, but as soon as the 'Save' button is clicked in the admin, I'm getting a validation error:
This field is required.
That makes sense, considering that my widget didn't render out a form field. However, what I'd like to do is basically remove this field from the update process: whenever used in the admin, it just shouldn't be mentioned in the SQL UPDATE at all.
Is that possible?
Here's a sketch of the code I have so far:
class MyWidget(Widget):
def render(self, name, value, attrs=None):
if value is None:
value = ""
else:
# pretty print the contents of value here
return '<table>' + ''.join(rows) + '</table>'
class MyField(JSONField):
def __init__(self, *args, **kwargs):
kwargs['null'] = False
kwargs['default'] = list
super(MyField, self).__init__(*args, **kwargs)
def formfield(self, **kwargs):
defaults = {
'form_class': JSONFormField,
'widget': MyWidget,
}
defaults.update(**kwargs)
return super(MyField, self).formfield(**defaults)
UPDATE 1: The use case is that the field represents an audit log. Internally, it will be written to regularly. The admin however never needs to write to it, it only has to render it out in a very readable format.
I'm not using any other ModelForms in the application, so the admin is the only form-user. I don't want to implement the behavior on the admin classes themselves, because this field will be reused across various models and is always supposed to behave the same way.
There are multiple ways to create a read-only field in the admin pages. Your requirements on the database storage are a bit fuzzy so I go through the options.
You have to register an AdminModel first in admin.py:
from django.contrib import admin
from yourapp.models import YourModel
class YourAdmin(admin.ModelAdmin):
pass
admin.site.register(YourModel, YourAdmin)
Now you can add different behavior to it. For example you can add the list of fields shown in the edit/add page:
class YourAdmin(admin.ModelAdmin):
fields = ['field1', 'field2']
This can be names of the model fields, model properties or model methods. Methods are displayed read-only.
If you want to have one field read-only explicitly add this:
class YourAdmin(admin.ModelAdmin):
fields = ['field1', 'field2']
readonly_fields = ['field2']
Then you have the option to overwrite the display of the field completely by adding a method with the same name. You will not even need a model field/method with that name, then:
class YourAdmin(admin.ModelAdmin):
fields = ['field1', 'field2']
readonly_fields = ['field2']
def field2(self, obj):
return '*** CLASSIFIED *** {}'.format(obj.field2)
With django.utils.safestring.mark_safe you can return HTML code as well.
All other options of the Admin are available, except the widget configuration as it applies to the writable fields only.
I might be a little confused as to what you want but you might want to look into model properties. Here is an example for my current project.
Code inside your model:
class Textbook(models.Model):
#other fields
#property
def NumWishes(self):
return self.wishlist_set.count()
Then you can just display it on the admin page.
class Textbook_table(admin.ModelAdmin):
fields = ["""attributes that are saved in the model"""]
list_display = ("""attributes that are saved in the model""", 'NumWishes'')
So now I can display NumWishes in the admin page but it doesn't need to be created with the model.
Hello in the class admin modify the permission method
#admin.register(my_model)
class My_modelAdmin(admin.ModelAdmin):
def has_delete_permission(self, request, obj=None):
return False
def has_change_permission(self, request, obj=None):
return False

Adding a cms plugin to a placeholder from code

I'm trying to add a plugin to a PlaceholderField from code.
I have a model (Question) with a few fields, one of them is a PlaceholderField.
What I want to do is adding a TextPugin (or any other generic cms_plugin) to that Placeholder Field. This is needed as I don't want people to add the TextPlugin manually from the frontend edit mode of the cms, but rather creating it myself so they can just add the right content after.
I know there's add_plugin from cms.api, but still I'd need to figure out a way to convert the PlaceholderField to Placeholder for it to work.
This is the code I have right now.
models.py
from django.utils.translation import ugettext as _
from django.db import models
from djangocms_text_ckeditor.cms_plugins import TextPlugin
from cms.models.fields import PlaceholderField
from cms.api import add_plugin
class Question(models.Model):
topic = models.ForeignKey('Topic')
question = models.CharField(_("Question"),max_length=256)
answer = PlaceholderField ('Answer plugin')
priorityOrder = models.IntegerField(_("Priority Order"))
def save(self, *args, **kwargs):
# Here's the critical point: I can cast self.answer to PlaceholderField,
# but I can't cast it to a Placeholder or add a placeholder to it
add_plugin( ????, plugin_type='TextPlugin', language='us',)
super(Question, self).save(*args, **kwargs)
# set the correct name of a django.model object in the admin site
def __unicode__(self):
return self.question
class Topic(models.Model):
title = models.CharField(_("Topic title"),max_length=256)
priorityOrder = models.IntegerField(_("Priority Order"))
# set the correct name of a django.model object in the admin site
def __unicode__(self):
return self.title
Any help (including alternative ways of doing this) is really welcome!
A PlaceholderField is nothing but a ForeignKey that auto-creates the relation to a new Placeholder object when a new instance is created.
As a result, you cannot use add_plugin on a PlaceholderField on an unsaved instance. You need to call super().save() first, then call add_plugin(self.answer, ...).

Can model views in Flask-Admin hyperlink to other model views?

Let's suppose we have a model, Foo, that references another model, User - and there are Flask-Admin's ModelView for both.
On the Foo admin view page
I would like the entries in the User column to be linked to the corresponding User model view.
Do I need to modify one of Flask-Admin's templates to achieve this?
(This is possible in the Django admin interface by simply outputting HTML for a given field and setting allow_tags (ref) True to bypass Django's HTML tag filter)
Some example code based on Joes' answer:
class MyFooView(ModelView):
def _user_formatter(view, context, model, name):
return Markup(
u"<a href='%s'>%s</a>" % (
url_for('user.edit_view', id=model.user.id),
model.user
)
) if model.user else u""
column_formatters = {
'user': _user_formatter
}
Use column_formatters for this: https://flask-admin.readthedocs.org/en/latest/api/mod_model/#flask.ext.admin.model.BaseModelView.column_formatters
Idea is pretty simple: for a field that you want to display as hyperlink, either generate a HTML string and wrap it with Jinja2 Markup class (so it won't be escaped in templates) or use macro helper: https://github.com/mrjoes/flask-admin/blob/master/flask_admin/model/template.py
Macro helper allows you to use custom Jinja2 macros in overridden template, which moves presentational logic to templates.
As far as URL is concerned, all you need is to find endpoint name generated (or provided) for the User model and do url_for('userview.edit_view', id=model.id) to generate the link.
extra information for #wodow, notice that model.user is wrong if you use pymongo as the backend, because the model in pymongo is a dict type, you can just use model['name'] to replace it
Adding this code to each of your models that have referenced by other models and flask-admin and jinja will take care of the name you want to display on the screen, just replace that with whatever you prefer:
def __unicode__(self):
return self.name # or self.id or whatever you prefer
for example:
class Role(db.Document, RoleMixin):
name = db.StringField(max_length=80, unique=True)
description = db.StringField(max_length=255)
def __unicode__(self):
return self.name
class MasterUser(db.Document, UserMixin):
email = db.StringField(max_length=255)
password = db.StringField(max_length=255)
active = db.BooleanField(default=True)
confirmed_at = db.DateTimeField()
roles = db.ListField(db.ReferenceField(Role), default=[])

How can i extend a model through a reusable app?

I wrote an app that is mainly allowing the user to drag tags to objects via jQuery. I want to allow that app to work for multiple models, so that i can tag ie. a user or an image. For this i thought about adding a class containing a "dropcode" to each models representation on the page:
<div class="droppable" dropcode="drop_img"> some image </div>
<div class="droppable" dropcode="drop_user"> some user </div>
I would like to specify the "dropcode" for each of the models in the main projects settings:
droppable_models={User:'drop_user',Image:'drop_img'}
After installing the app, i want to be able to retrieve the dropcode from each instance of the affected models:
image_instance1.dropcode -> drop_img
image_instance2.dropcode -> drop_img
user_instance1.dropcode -> drop_user
user_instance2.dropcode -> drop_user
That way i could just simply use the dropcode on the page, return it via jQuery to select the right model
Is that possible? Is there a better way to achieve what i want do do?
Why not add a dropcode property to the appropriate models? eg.
class Image(models.Model):
....
dropcode = property(lambda self: "drop_img")
For existing models where you can't edit the models.py (such as User model), add code like this to the models.py of one of your own apps:
from django.contrib.auth.models import User
class UserMixin:
dropcode = property(lambda self: "drop_user")
User.__bases__ += (UserMixin,)
Then in your template, use an if tag to check whether an item has a dropcode. You can therefore eliminate the droppable_models setting:
<div class="droppable"{% if item.dropcode %} dropcode="{{item.dropcode}}"{% endif %}>{{item}}</div>
If your application should work with any model, then you should use the contentypes framework:
Django includes a contenttypes application that can track all of the
models installed in your Django-powered project, providing a
high-level, generic interface for working with your models.
Implementing this allows your application to be generic - it can work with any installed model.
EDIT:
Here is how to use content types framework (directly from the documentation):
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
class TaggedItem(models.Model):
tag = models.SlugField()
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
def __unicode__(self):
return self.tag
Now, to add a tag to the item:
item = Model.object.get(pk=1)
ti = TaggedItem(tag='foo',content_object=item)
ti.save()
To get tags for a particular item:
i = Image.object.get(pk=1) # or any instance of the Image object, or any object
the_type_of_object = ContentType.objects.get_for_model(i)
# Find all tags for this object
image_tags = TaggedItem.objects.filter(content_type__pk=the_type_of_object.id,
object_id=i.id)
Based on the tips of Simon and Burhan i came to the following solution: I define the affected models in the settings and then add the DragToTagable Class as Base Class to those models. This look like that in the settings:
DROPPABLE_MODELS=('test.TestItem:item',)
Thats all that needs to be done to apply the apps functionality to that model, or any other model of my project. The model.py of my app looks like this now:
from django.contrib.contenttypes.models import ContentType
from django.conf import settings
try:
#perform this when starting the project
for definition in settings.DROPPABLE_MODELS:
#parse contenttype
parsed=definition.split(':')
dropcode=parsed[1]
parsed=parsed[0].split('.')
appname=parsed[0]
modelname=parsed[1]
#get the models class for the specified contenttype
model_class=ContentType(app_label=appname, model=modelname).model_class()
#create class Mixin, containing the dropcode property
class DragToTagable:
dropcode = dropcode
#add DragToTagable as a base class to the model class
model_class.__bases__+=(DragToTagable,)
except AttributeError:
pass
except:
import sys
print "Unexpected error:", sys.exc_info()[0]
raise
This way i do not have to create an additional table, like in burhans proposal. And the app stays completely independent and requires no work on existing models to be implemented.
Thanks for the tips.

Django admin, filter objects by ManyToMany reference

There's photologue application, simple photo gallery for django, implementing Photo and Gallery objects.
Gallery object has ManyToMany field, which references Photo objects.
I need to be able to get list of all Photos for a given Gallery. Is it possible to add Gallery filter to Photo's admin page?
If it's possible, how to do it best?
You need to write a custom FilterSpec! Custom Filter in Django Admin on Django 1.3 or below
It'll look like this:
from django.contrib.admin.filterspecs import RelatedFilterSpec, FilterSpec
from models import Gallery
class GalleryFilterSpec(RelatedFilterSpec):
def __init__(self, f, request, params, model, model_admin):
self.lookup_kwarg = f.name
self._lookup_model = f.rel.to
self.lookup_val = request.GET.get(self.lookup_kwarg, None)
self.user = request.user
self.lookup_choices = [(g.pk, g.name) for g in Gallery.objects.all()]
def has_output(self):
return len(self.lookup_choices) > 1
def title(self):
return self._lookup_model._meta.verbose_name
FilterSpec.filter_specs.insert(0,
(lambda f: f.rel.to == Gallery, GalleryFilterSpec))
Put it in a module filters.py in your app package and import it in you admin.py (it's important to import it, so that the filter becomes registered on the admin site!)
EDIT: "f" is the field instance, in this case models.ManyToManyField The last line registers the FilterSpec for all fields that have a relation to the Gallery model. This will not work as you mentioned if the field is defined on the Gallery model, since django.contrib.admin.views.main.ChangeList.get_filters checks if the field you define in the list really exist on the model (doesnt work for related_name either). I think the easiest way around is that you could make a custom template for that changelist and hardcode your filter in there, the FilterSpec itself isn't need for the filtering itself, django uses just the url get parameters for that!
Well, that's how I've done it.
I made custom admin template "change_list.html". Custom template tag creates a list of all existing galleries. Filtering is made like this:
class PhotoAdmin(admin.ModelAdmin):
...
def queryset(self, request):
if request.COOKIES.has_key("gallery"):
gallery = Gallery.objects.filter(title_slug=request.COOKIES["gallery"])
if len(gallery)>0:
return gallery[0].photos.all()
return super(PhotoAdmin, self).queryset(request)
Cookie is set with javascript.
For future reference for others to find, if you have a relationship it's bi-directional, so you can get the photos for galleries or the galleries for a photo via a ModelAdmin.
Let's say you have a changelist view for your Photo model:
from django.contrib import admin
from yourapp.models import Photo
class PhotoAdmin(admin.ModelAdmin):
list_filter = ('galleries', )
admin.site.register(Photo, PhotoAdmin)
Then in the admin you'll see a filter showing all of the galleries and if you click one it'll filter the list to show you only photos for that gallery.
Of course, this may not be practical if there are a LOT of galleries, but you can get there just by using the well-documented ModelAdmin rather than hacking together a template or filterspec.
http://docs.djangoproject.com/en/dev/ref/contrib/admin/#modeladmin-objects
#Jough Dempsey pointed out you maybe don't need a custom FilterSpec just for m2m fields.
However today I found I wanted one for a django-taggit tag field. The tags are basically an m2m relation but it complains that 'TaggableManager' object has no attribute 'get_choices' if you try and add the tag field into list_filter.
In this case it was #lazerscience's code to the rescue...
However it didn't work when used against Django 1.3, needed a couple of new lines added, compare my version below which works:
class TagFilterSpec(RelatedFilterSpec):
def __init__(self, f, request, params, model, model_admin, field_path=None):
super(RelatedFilterSpec, self).__init__(
f, request, params, model, model_admin, field_path=field_path)
self.lookup_title = f.verbose_name # use field name
self.lookup_kwarg = f.name
self.lookup_kwarg_isnull = '%s__isnull' % (self.field_path)
self._lookup_model = f.rel.to
self.lookup_val = request.GET.get(self.lookup_kwarg, None)
self.lookup_val_isnull = request.GET.get(
self.lookup_kwarg_isnull, None)
self.user = request.user
self.lookup_choices = [(g.pk, g.name) for g in Tag.objects.all()]
def has_output(self):
return len(self.lookup_choices) > 1
def title(self):
return self._lookup_model._meta.verbose_name
FilterSpec.filter_specs.insert(0,
(lambda f: f.rel.to == Tag, TagFilterSpec))

Categories