Implement html output for a model field in Django - python

I've another quick question, I need to use very often a specific html representation for a model field. I know there is a lot of documentation for forms but what about simple model field ? For instance :
models.py
class Status (models.Model):
order = models.PositiveIntegerField(_(u'Order'))
name = models.CharField(_(u'Name'), max_length=255, unique=True, db_index=True)
color = models.CharField(_(u'Color'), max_length=6, null=True, blank=True)
template.html
{{status.order.as_span}} will be equivalent to <span>{{status.order}}<span>
The first idea I have is to implement a function inside a manager but seems to break the MVC rules ... There is a proper way for this ?

You shouldn't assemble html in your view code, this is what template tags are for. Put this in eg. <app>/templatetags/<app>tags.py (replace <app> with your app name):
from django.utils.safestring import mark_safe
from django import template
register = template.Library()
#register.simple_tag
def spanme(fld):
return mark_safe("<span>%s</span>" % fld)
and use it as:
{% load <app>tags %}
...
{% spanme status.order %}

It is possible to subclass the model field also:
from django.utils.safestring import mark_safe
from django.db.models.fields import CharField
from django.db import models
class SpanValue(unicode):
def as_span(self):
return mark_safe(u"<span>%s</span>" % self)
class SpanField(CharField):
__metaclass__ = models.SubfieldBase
def to_python(self, value):
if value is None:
return None
return SpanValue(value)
class SpanModel(models.Model):
foo = SpanField(max_length=25)
then you could use it like this:
from django import template
from maint.models import *
#val = SpanModel.objects.create(foo='bar')
val = SpanModel.objects.get(foo='bar') # must get it from the db for `to_python` to run
t = template.Template(u"""{{ val.foo.as_span }}""")
print t.render(template.Context({'val': val}))
which would print
<span>bar</span>
Doing something like this would be very surprising to anyone else, so please never ever do this :-)
ps: if you inherit from something that isn't a text field you might be able to override __unicode__ (so you wouldn't have to use .as_span in the template), however that is considerably more work.

Related

How to use autocompleteselect widget in a modelform

I know there is a new feature in Django 2.0, which is AutocompleteSelect widget in ModelAdmin.
I am trying to use it in my custom modelForm but just failed.
Tried like this
#unit is the foreign key to the incident
class AccountForm(forms.ModelForm):
class Meta:
model = Invoice
...
...
widgets = { 'incident':widgets.AutocompleteSelect(Invoice._meta.get_field('incident').remote_field, admin.site)
}
...
#Invoice model
class Invoice(models.Model):
...
incident = models.ForeignKey(Unit, on_delete=models.CASCADE,null=True)
...
Hope anyone can help me.
Thanks
The AutocompleteSelect widget will not work outside of the admin site.
If you are using AccountForm in admin site you can use the following code:
class AccountForm(forms.ModelForm):
...
incident = forms.ModelChoiceField(
queryset= Unit.objects.all(),
widget=AutocompleteSelect(Invoice.incident.field.remote_field, admin.site),
)
...
class Meta:
model = Invoice
fields = [
'incident',
...
]
#admin.register(Invoice)
class InvoiceAdmin(admin.ModelAdmin):
form = AccountForm
AutocompleteSelect has 2 required args, rel and admin_site. The rel is used to extract the model used to query the data from and relates to an attribute on a ForeignKey or ManyToManyField. Since I wanted to use this on a field that wasn't actually a ForeignKey, I needed to override a few things to make it work:
class ClientAutocompleteSelect(AutocompleteSelect):
def get_url(self):
model = Client
return reverse(self.url_name % (self.admin_site.name, model._meta.app_label, model._meta.model_name))
class ClientChoiceField(forms.ModelChoiceField):
def __init__(self, queryset=None, widget=None, **kwargs):
if queryset is None:
queryset = Client.objects.all()
if widget is None:
widget = ClientAutocompleteSelect(None, admin.site) # pass `None` for `rel`
super().__init__(queryset, widget=widget, **kwargs)
def to_python(self, value):
return value # just return the client_id and not a Client object
class MyAdminForm(forms.ModelForm):
client_id=ClientChoiceField()
...
This requires that the end user has admin read access to the autocomplete endpoint of the model being queried. You may be able to do more hacking to change that get_url and use your own endpoint to give search results, though.
I spent a few hours trying to understand why my code (built on #Tim 's answer) would not work, until I stumble on a comment about missing references to css/js files.
Here is a complete working solution to use the AutocompleteSelect widget in any custom form for signed-in users having both 'staff' and 'view' access to the given model:
from django.urls import reverse
from django.contrib.admin.widgets import AutocompleteSelect
from django.contrib import admin
class UserAutocompleteSelect(AutocompleteSelect):
def get_url(self):
model = CustomUser
return reverse(self.url_name % (self.admin_site.name, model._meta.app_label, model._meta.model_name))
class UserChoiceField(forms.ModelChoiceField):
def __init__(self, queryset=None, widget=None, **kwargs):
if queryset is None:
queryset = CustomUser.objects.all()
if widget is None:
widget = UserAutocompleteSelect(None, admin.site) # pass `None` for `rel`
super().__init__(queryset, widget=widget, **kwargs)
class UserAutocompleteSelectForm(forms.ModelForm):
"""
for changing user on Play objects
using amdin module autocomplete
"""
user = UserChoiceField(
# queryset=CustomUser.objects.all(),
help_text=_('Select the user to replace the current one')
)
class Meta:
model = Play
fields = ('user', )
You can use the same, replacing CustomUser and Play by your own models
If (like me) this is not working out-of-the-box with the html template you're using, that means that you need to include the required css/js files to your template. Here is a simple way to do it :
Providing that the form is declared as such in the view:
form = UserAutocompleteSelectForm()
...
context = {
'form': form,
...
}
return render(request, 'users/change_user.html', context)
you should add the following lines to the html template to include the required css/js files:
{% block extrahead %}
{{ block.super }}
{{ form.media }}
{% endblock %}

How to set a variable from one class equals to a variable in another class in Django models.py?

I am a new in Django world and I want to link two classes from models.py so that i can set their variables equal to each other. Here is the models.py code:
from django.db import models
from django.core.urlresolvers import reverse
# Create your models here.
class file(models.Model):
title = models.CharField(max_length=250)
FILE_TYPE_CHOICES = (
('audio','Audio'),
('games','Games'),
('videos','Videos'),
('applications','Applications'),
('books','Books/Docs'),
('others','Others')
)
file_type = models.CharField(max_length=10,choices=FILE_TYPE_CHOICES,default='others')
description = models.TextField(max_length=6000)
#uploader_username = ???
def get_absolute_url(self):
return reverse('one:user')
def __str__(self):
return self.title
class user (models.Model):
username= models.CharField(max_length=100)
email=models.EmailField
password= models.CharField(max_length = 100)
user_files = models.ForeignKey(file, on_delete=models.CASCADE)
Here I want to set uploader_username from file class equals tousername from user class.
No, you don't want to do this. You want a ForeignKey from File to User, not the other way round, then you can just access my_file.user.username.
Note, it is a bad idea to define your own user class like this. There can be good reasons for doing so, but if so you must inherit from the abstract base classes in the auth app; failure to do so is a serious security problem as you will be storing passwords in clear text. It doesn't look like you need your own model here; you should remove this class.

django urls from models

I have the following model:
class Articles(models.Model):
status = models.CharField(choices=STATUSES, max_length=10, default='online')
categ = models.ForeignKey(ArticlesCategories)
title = models.CharField(max_length=64)
content = models.TextField()
def get_edit_url(self):
return '/articles/add/%d' % self.pk
edit_url = property(get_edit_url)
def __unicode__(self):
return self.title
class Meta:
db_table = 'articles'
and the url route:
url(r'^add/(?P<article_id>\d+)$', views.add_article, name='edit_articles'),
How do I have to edit the "get_edit_url" function so the url for edit article to be generated dynamically and to be able to change the path only from urls.py ?
Thank you
This is a common problem, django provides the tools to do it using the reverse function
from django.core.urlresolvers import reverse
def get_edit_url(self):
# assuming your app isn't namespaced
return reverse('edit_articles', args=(self.pk,))
# if your app was namespaced it would be something like
# reverse('articles:edit_articles')
I generally use reverse with model method:
from django.db import models
from django.core.urlresolvers import reverse
class MyModel(models.Model):
...
def get_detail_url(self):
return reverse('article-detail', args=(self.pk,))
def get_list_url(self):
return reverse('article-list')
This approach is more RESTful, detail_view will allow to get,delete,update an instance and list_view will allow to get list or eventually make bulk delete/update.
You could use django-model-urls to do that automatically without even altering your model!
Using your example:
urls.py
url(r'^add/(?P<id>\d+)$', views.add_article, name='edit_articles'),
some template:
{{ url('edit_articles', article) }}
(Note that current version use jingo, raw Django template can use the 0.3.1 version)

Add a counter to Django admin home

I want to know if it is possible to add a counter to Django admin app home page,
example:
Can I create a #property in models.py or admin.py for this?
Thanks
You can specialize the string type to add your desired dynamic behavior.
Here is a complete example:
from django.db import models
class VerboseName(str):
def __init__(self, func):
self.func = func
def decode(self, encoding, erros):
return self.func().decode(encoding, erros)
class UsedCoupons(models.Model):
name = models.CharField(max_length=10)
class Meta:
verbose_name_plural = VerboseName(lambda: u"Used Coupons (%d)" % UsedCoupons.objects.all().count())
I think you can't do it in Meta, since it is needs a dynamic value. Also modifying Meta would make the change in all places you use the model, not just the admin. I think the best idea would be to use something like this https://stackoverflow.com/a/6312798/1433029 or use django admin tools https://bitbucket.org/izi/django-admin-tools/

Python Django Forms Multiple Select HTML Ouput

I'm trying to output the description field from my model
in a django form without joy.
After searching around and not finding what I need
I hope I can ask here.
Here is my models, form, template and template output.
I've abrieviated to help make this post concise.
I'm working on the view in this project so the model
has been designed by someone else and I can't really change it.
MODELS:
1)
from django.db import models
class Project(models.Model):
description = models.TextField(blank = True)
name = models.CharField(max_length = 255, blank = True)
def __unicode__(self):
""" String representation of projects. """
return unicode(self.name)
2)
from django.db import models
class Share(models.Model):
description = models.TextField
last_access = models.DateTimeField(auto_now = True)
location = models.URLField(verify_exists = False)
project = models.ForeignKey('Project', related_name = 'shares')
def __unicode__(self):
return unicode(self.location)
FORM:
from django import forms
from models import Share
class ExportForm(forms.Form):
ps = forms.ModelMultipleChoiceField(queryset=Share.objects.filter(project=1),widget=forms.SelectMultiple())
VIEW:
form = ExportForm()
TEMPLATE:
I have this to ouput the multiple select bos:
{{ form.ps }}
TEMPLATE OUTPUT:
<select multiple="multiple" name="ps" id="id_ps">
<option value="11">Share object </option>
<option value="10">Share object </option>
</select>
I've tried several things from searching around but can't
seem to be able to make the 'description' field in there rather than 'Share object'
Any advise much appreciated.
Thanks!
The easiest way to do this is to change the __unicode__ method of the Share model to return description instead of location, but since you say you can't change the model you will need to subclass ModelMultipleChoiceField and override the label_from_instance method.
class MyModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
return obj.description
This is explained in the documentation.

Categories

Resources