This is an offshoot of the generic how can I include properties in a JSON serialization, which is answered here: https://stackoverflow.com/a/38253327/4140357
from django.core.serializers.base import Serializer as BaseSerializer
from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.json import Serializer as JsonSerializer
class ExtBaseSerializer(BaseSerializer):
def serialize_property(self, obj):
model = type(obj)
for field in self.selected_fields:
if hasattr(model, field) and type(getattr(model,
field)) == property:
self.handle_prop(obj, field)
def handle_prop(self, obj, field):
self._current[field] = getattr(obj, field)
def end_object(self, obj):
self.serialize_property(obj)
super(ExtBaseSerializer, self).end_object(obj)
class ExtPythonSerializer(ExtBaseSerializer, PythonSerializer):
pass
class ExtJsonSerializer(ExtPythonSerializer, JsonSerializer):
pass
How to use it:
ExtJsonSerializer().serialize(
MyModel.objects.all(),
fields=['field_name_1', 'property_1' ...]
)
And it works great for JSON.
How can you do the same thing for GEODJango's GEJSON serializer?
It is quite straightforward to alter the original answer for JSON to work with GEOJSON.
You need to change one line in the imports so instead of importing
from django.core.serializers.json import Serializer as JsonSerializer you import the GEOJSON one
from django.contrib.gis.serializers.geojson import Serializer as JsonSerializer
Here's the full code for ease of cut-n-paste.
from django.core.serializers.base import Serializer as BaseSerializer
from django.core.serializers.python import Serializer as PythonSerializer
from django.contrib.gis.serializers.geojson import Serializer as JsonSerializer
class ExtBaseSerializer(BaseSerializer):
def serialize_property(self, obj):
model = type(obj)
for field in self.selected_fields:
if hasattr(model, field) and type(getattr(model,
field)) == property:
self.handle_prop(obj, field)
def handle_prop(self, obj, field):
self._current[field] = getattr(obj, field)
def end_object(self, obj):
self.serialize_property(obj)
super(ExtBaseSerializer, self).end_object(obj)
class ExtPythonSerializer(ExtBaseSerializer, PythonSerializer):
pass
class ExtJsonSerializer(ExtPythonSerializer, JsonSerializer):
pass
How to use it:
ExtJsonSerializer().serialize(
MyModel.objects.all(),
fields=['field_name_1', 'property_1' ...]
)
Related
i want use import export to bulk user import in django
i get a file include list of users then create users according to file rows
i try implement before_import like this
from import_export import resources
class UserResource(resources.ModelResource):
def before_import(self,dataset, dry_run, **kwargs):
#dataset is tablib.Dataset()
for i in dataset:
i[1]=make_password(i[1])
return super(UserResource, self).before_import(dataset, dry_run, **kwargs)
but it return tuple' object does not support item assignment
For Passwords you could write your own password widget, which turns the plain password into a hash. Like this (untested):
class PassWidget(Widget):
def clean(self, value):
if self.is_empty(value):
return None
return make_password(value)
def render(self, value):
return force_text(value)
I hope you are trying to hash your password before importing.
it helps you:
from import_export import resources, fields
from import_export.admin import ImportExportModelAdmin
from django.contrib.auth.hashers import make_password
class UserResource(resources.ModelResource):
groups = fields.Field(
column_name='group_name',
attribute='groups',
widget=ManyToManyWidget(Group, ',','name')
)
def before_import_row(self,row, **kwargs):
value = row['password']
row['password'] = make_password(value)
class Meta:
model = User
I'm trying to make a custom form field in Django that allows a user to link a file or upload a file. To do this, I'm creating a subclass of the MultiValueField with the fields property set to (URLField(), FileField()). I'm not sure this is the right approach, but I keep getting an error I can't understand:
'MyFileField' object has no attribute 'attrs'
Here is my code. Can anyone explain what's going on?
from django import forms
from django.core.exceptions import ValidationError
from .models import Case, Person, Opp, Category
class MyFileField(forms.MultiValueField):
def compress(self, data_list):
# I'll get to this once the attr error goes away
pass
def __init__(self, *args, **kwargs):
fields = (
forms.URLField(), forms.FileField()
)
super(MyFileField, self).__init__(fields=fields, *args, **kwargs)
class CaseForm(forms.ModelForm):
class Meta:
model = Case
fields = ['title', 'file', 'categories']
widgets = {
'file': MyFileField
}
The problem is that you are calling init on an abstract class.
super(MyFileField, self).__init__(fields=fields, *args, **kwargs)
But the base class is abstract.
Look at https://docs.djangoproject.com/en/1.8/ref/forms/fields/#multivaluefield
and
Python's super(), abstract base classes, and NotImplementedError
My app has users who create pages. In the Page screen of the admin, I'd like to list the User who created the page, and in that list, I'd like the username to have a link that goes to the user page in admin (not the Page).
class PageAdmin(admin.ModelAdmin):
list_display = ('name', 'user', )
list_display_links = ('name','user',)
admin.site.register(Page, PageAdmin)
I was hoping that by making it a link in the list_display it would default to link to the actual user object, but it still goes to Page.
I'm sure I'm missing something simple here.
Modifying your model isn't necessary, and it's actually a bad practice (adding admin-specific view-logic into your models? Yuck!) It may not even be possible in some scenarios.
Luckily, it can all be achieved from the ModelAdmin class:
from django.urls import reverse
from django.utils.safestring import mark_safe
class PageAdmin(admin.ModelAdmin):
# Add it to the list view:
list_display = ('name', 'user_link', )
# Add it to the details view:
readonly_fields = ('user_link',)
def user_link(self, obj):
return mark_safe('{}'.format(
reverse("admin:auth_user_change", args=(obj.user.pk,)),
obj.user.email
))
user_link.short_description = 'user'
admin.site.register(Page, PageAdmin)
Edit 2016-01-17:
Updated answer to use make_safe, since allow_tags is now deprecated.
Edit 2019-06-14:
Updated answer to use django.urls, since as of Django 1.10 django.core.urls has been deprecated.
Add this to your model:
def user_link(self):
return '%s' % (reverse("admin:auth_user_change", args=(self.user.id,)) , escape(self.user))
user_link.allow_tags = True
user_link.short_description = "User"
You might also need to add the following to the top of models.py:
from django.template.defaultfilters import escape
from django.core.urls import reverse
In admin.py, in list_display, add user_link:
list_display = ('name', 'user_link', )
No need for list_display_links.
You need to use format_html for modern versions of django
#admin.register(models.Foo)
class FooAdmin(admin.ModelAdmin):
list_display = ('ts', 'bar_link',)
def bar_link(self, item):
from django.shortcuts import resolve_url
from django.contrib.admin.templatetags.admin_urls import admin_urlname
url = resolve_url(admin_urlname(models.Bar._meta, 'change'), item.bar.id)
return format_html(
'{name}'.format(url=url, name=str(item.bar))
)
I ended up with a simple helper:
from django.shortcuts import resolve_url
from django.utils.safestring import SafeText
from django.contrib.admin.templatetags.admin_urls import admin_urlname
from django.utils.html import format_html
def model_admin_url(obj: Model, name: str = None) -> str:
url = resolve_url(admin_urlname(obj._meta, SafeText("change")), obj.pk)
return format_html('{}', url, name or str(obj))
Then you can use the helper in your model-admin:
class MyAdmin(admin.ModelAdmin):
readonly_field = ["my_link"]
def my_link(self, obj):
return model_admin_url(obj.my_foreign_key)
I needed this for a lot of my admin pages, so I created a mixin for it that handles different use cases:
pip install django-admin-relation-links
Then:
from django.contrib import admin
from django_admin_relation_links import AdminChangeLinksMixin
#admin.register(Group)
class MyModelAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
# ...
change_links = ['field_name']
See the GitHub page for more info. Try it out and let me know how it works out!
https://github.com/gitaarik/django-admin-relation-links
I decided to make a simple admin mixin that looks like this (see docstring for usage):
from django.contrib.contenttypes.models import ContentType
from django.utils.html import format_html
from rest_framework.reverse import reverse
class RelatedObjectLinkMixin(object):
"""
Generate links to related links. Add this mixin to a Django admin model. Add a 'link_fields' attribute to the admin
containing a list of related model fields and then add the attribute name with a '_link' suffix to the
list_display attribute. For Example a Student model with a 'teacher' attribute would have an Admin class like this:
class StudentAdmin(RelatedObjectLinkMixin, ...):
link_fields = ['teacher']
list_display = [
...
'teacher_link'
...
]
"""
link_fields = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.link_fields:
for field_name in self.link_fields:
func_name = field_name + '_link'
setattr(self, func_name, self._generate_link_func(field_name))
def _generate_link_func(self, field_name):
def _func(obj, *args, **kwargs):
related_obj = getattr(obj, field_name)
if related_obj:
content_type = ContentType.objects.get_for_model(related_obj.__class__)
url_name = 'admin:%s_%s_change' % (content_type.app_label, content_type.model)
url = reverse(url_name, args=[related_obj.pk])
return format_html('{}', url, str(related_obj))
else:
return None
return _func
If anyone is trying to do this with inline admin, consider a property called show_change_link since Django 1.8.
Your code could then look like this:
class QuestionInline(admin.TabularInline):
model = Question
extra = 1
show_change_link = True
class TestAdmin(admin.ModelAdmin):
inlines = (QuestionInline,)
admin.site.register(Test, TestAdmin)
This will add a change/update link for each foreign key relationship in the admin's inline section.
I need to convert a Django Queryset Object into a Json string. The built in Django Serialization library works great. Although it specifies the name of the Model from where it was created. Since I don't need this, how do I get rid of it? What else do I need to override to be able to use the overridden end_object method below?
class Serializer(PythonSerializer):
def end_object(self, obj):
self.objects.append({
"model" : smart_unicode(obj._meta), # <-- I want to remove this
"pk" : smart_unicode(obj._get_pk_val(), strings_only=True),
"fields" : fields
})
self._current = None
Sorry I had totally forgot about this question. This is how I ended up solving it (with thanks to FunkyBob on #django):
from django.core.serializers.python import Serializer
class MySerialiser(Serializer):
def end_object( self, obj ):
self._current['id'] = obj._get_pk_val()
self.objects.append( self._current )
# views.py
serializer = MySerialiser()
data = serializer.serialize(some_qs)
Here's a serializer that removes all metadata (pk, models) from the serialized output, and moves the fields up to the top-level of each object. Tested in django 1.5
from django.core.serializers import json
class CleanSerializer(json.Serializer):
def get_dump_object(self, obj):
return self._current
Serializer = CleanSerializer
Edit:
In migrating my app to an older version of django (precipitated by a change in hosting provider), the above serializer stopped working. Below is a serializer that handles the referenced versions:
import logging
import django
from django.core.serializers import json
from django.utils.encoding import smart_unicode
class CleanSerializer148(json.Serializer):
def end_object(self, obj):
current = self._current
current.update({'pk': smart_unicode(obj._get_pk_val(),
strings_only=True)})
self.objects.append(current)
self._current = None
class CleanSerializer151(json.Serializer):
def get_dump_object(self, obj):
self._current['pk'] = obj.pk
return self._current
if django.get_version() == '1.4.8':
CleanSerializer = CleanSerializer148
else:
CleanSerializer = CleanSerializer151
Serializer = CleanSerializer
Override JSON serializer class:
from django.core.serializers.json import Serializer, DjangoJSONEncoder
from django.utils import simplejson
class MySerializer(Serializer):
"""
Convert QuerySets to JSONS, overrided to remove "model" from JSON
"""
def end_serialization(self):
# little hack
cleaned_objects = []
for obj in self.objects:
del obj['model']
cleaned_objects.append(obj)
simplejson.dump(cleaned_objects, self.stream, cls=DjangoJSONEncoder, **self.options)
In the view:
JSONSerializer = MySerializer
jS = JSONSerializer()
For example I have a Article model for blog articles so it's easy to add articles to the database.
But when I need to edit them, in form if I create a form class by form.ModelForm, I can pass instace=artcile to the form and that's it.
But if I create a form class by form.Forms I have to declare a form instance and pass fields to the form one by one.
Something like this
form = ArticleForm({
'title': article.title,
'body': article.body,
'pub_date': article.pub_date
'status': article.status,
'author': article.author,
'comments': article.comments.count(),
'blah': article.blahblah,
'againBlah': article.againBlah,
.....
})
It's ugly, isn't?
Is there any way to do this shorter, without using form.ModelForm?
You can use the model_to_dict and fields_for_model utils from django.forms.models:
# assuming article is an instance of your Article model:
from django.forms import Form
from django.forms.models import fields_for_model, model_to_dict
form = Form(model_to_dict(article))
form.fields.update(fields_for_model(article))
If you have an m2m relation, you can create a formset for it:
from django import forms
from django.forms.models import model_to_dict, fields_for_model
from django.forms.formsets import formset_factory
# assuming your related model is called 'Tag'
class TagForm(forms.Form):
def __init__(self, *args, **kwargs):
super(TagForm, self).__init__(*args, **kwargs)
self.fields.update(fields_for_model(Tag))
TagFormSet = formset_factory(TagForm)
formset = TagFormSet(initial=[model_to_dict(tag) for tag in article.tags.all()])
Then you can iterate through the formset to access the forms created for the related models:
for form in formset.forms:
print form