Django 3 models.Q - app_lable gets displayed inside html - python

since I upgraded to Django 3.x I have a strage behaviour.
Imaging the following field at your models.py
content_type = models.ForeignKey(ContentType, limit_choices_to=filter_choice, on_delete=models.CASCADE, null=True, blank=True)
which refers to:
filter_choice = models.Q(app_label='App', model='model_x') | models.Q(app_label='App', model='model_y')
If I now display the content_type field on my html templates it look like this: "App| Model Y" which looks quite stupid, same goes for Django admin. Is this a Bug? I'm asking because on Django 2.2.7 (Latest version of 2.x) I dont had this behaviour and only model_x and model_y have been displayed as expected.
Would be awesome if only model_x and model_y getting displayd without there app lables. Is there any solution for this, maybe a new option that comes with django 3.x?
Thanks in advance :)

If I now display the content_type field on my html templates it look like this: "App| Model Y" which looks quite stupid.
This is how the __str__ of a ContentType is implemented. Indeed, if we take a look at the source code [GitHub], we see:
class ContentType(models.Model):
# …
def __str__(self):
return self.app_labeled_name
# …
#property
def app_labeled_name(self):
model = self.model_class()
if not model:
return self.model
return '%s | %s' % (model._meta.app_label, model._meta.verbose_name)
If you want to render the model name however, you can for example use:
{{ object.content_type.model_class._meta.verbose_name }}
It makes sense to include the app label, since the same model name can be used in different apps, hence it is possible that your project has two Model Ys, in two different apps.
Furthermore it is not very common to render a ContentType in the template. Normally this is part of the technical details of your project, that you likely do not want to expose. If you need to show the type of the object in a GenericForeignKey, you can simply follow the GenericForeignKey, and render the ._meta.verbose_name of that object.

Related

An efficient way to save parsed XML content to Django Model

This is my first question so I will do my best to conform to the question guidelines. I'm also learning how to code so please ELI5.
I'm working on a django project that parses XML to django models. Specifically Podcast XMLs.
I currently have this code in my model:
from django.db import models
import feedparser
class Channel(models.Model):
channel_title = models.CharField(max_length=100)
def __str__(self):
return self.channel_title
class Item(models.Model):
channel = models.ForeignKey(Channel, on_delete=models.CASCADE)
item_title = models.CharField(max_length=100)
def __str__(self):
return self.item_title
radiolab = feedparser.parse('radiolab.xml')
if Channel.objects.filter(channel_title = 'Radiolab').exists():
pass
else:
channel_title= radiolab.feed.title
a = Channel.objects.create(channel_title=channel_title)
a.save()
for episode in radiolab.entries:
item_title = episode.title
channel_title = Channel.objects.get(channel_title="Radiolab")
b = Item.objects.create(channel=channel_title, item_title=item_title)
b.save()
radiolab.xml is a feed I've saved locally from Radiolab Podcast Feed.
Because this code is run whenever I python manage.py runserver, the parsed xml content is sent to my database just like I want to but this happens every time I runserver, meaning duplicate records.
I'd love some help in finding a way to make this happen just once and also a DRY mechanism for adding different feeds so they're parsed and saved to database preferably with the feed url submitted via forms.
If you don't want it run every time, don't put it in models.py. The only thing that belongs there are the model definitions themselves.
Stuff that happens in response to a user action on the site goes in a view. Or, if you want this to be done from the admin site, it should go in the admin.py file.

Django dynamic name values in model overview on admin site

What i try to archive is having dynamic information about models, like the count, on the default admin view page where all the registered models are listed.
I do it now in an extremely hackish way, like:
class x(object):
def __unicode__(self, *args, **kwargs):
return u"Items (%i items)" % SteamItem.objects.count()
class Item(models.Model)
...
class Meta:
verbose_name_plural = x()
Is there a way to do the same in a nicer way without having to edit the admin template ?
If editing the admin template is the only way I'd appreciate some hints where to start my search.
Introspecting the django admin code I found out that unfortunately the model class is not being passed to the template context - so you cannot easily query for object count. What is passed to the template for every model is:
model_dict = {
'name': capfirst(model._meta.verbose_name_plural),
'object_name': model._meta.object_name,
'perms': perms,
}
What you could do is override AdminSite._build_app_dict to include the model class itself, override the default index template and the just query in the template using:
{{ model_class.objects.count }}
The default template is admin/index.html but can be configured on per admin site basis as you can see in the above mentioned class.

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