Django: change admin FileField output? - django

I've made a model with file that is uploaded to custom path (not in MEDIA_ROOT). So it's some kind like protected file.
Now I need to change it's representation in admin details. It shows a path relative to MEDIA_URL. I need to change that, to show a URL to an application view which generates a proper URL.
So, what is the best way to display link, and only in objects details in admin?

Here is the way I did it:
models.py
class SecureFile(models.Model):
upload_storage = FileSystemStorage(
location=settings.ABS_DIR('secure_file/files/'))
secure_file = models.FileField(verbose_name=_(u'file'),
upload_to='images', storage=upload_storage)
widgets.py
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse
from django.utils.safestring import mark_safe
class AdminFileWidget(forms.FileInput):
"""A FileField Widget that shows secure file link"""
def __init__(self, attrs={}):
super(AdminFileWidget, self).__init__(attrs)
def render(self, name, value, attrs=None):
output = []
if value and hasattr(value, "url"):
url = reverse('secure_file:get_secure_file',
args=(value.instance.slug, ))
out = u'{}<br />{} '
output.append(out.format(url, _(u'Download'), _(u'Change:')))
output.append(super(AdminFileWidget, self).render(name, value, attrs))
return mark_safe(u''.join(output))
admin.py
class SecureFileAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SecureFileAdminForm, self).__init__(*args, **kwargs)
self.fields['secure_file'].widget = AdminFileWidget()
class Meta:
model = SecureFile
class SecureFileAdmin(admin.ModelAdmin):
form = SecureFileAdminForm

Related

flask-admin "AdminIndexView" not working to restrict the /admin page

this is my first question in stackoverflow.
i need to change flask_admin's default page but it's not working, i want to make it inaccessible for non-admin users...
admin file:
from flask import redirect, url_for
from flask_admin import AdminIndexView
from flask_admin.contrib.sqla import ModelView
from flask_login import current_user
from wtforms.fields import FileField
from wtforms.validators import DataRequired
from . import adm, db
from .models import User, Product
def configure():
adm.index_view = IndexAdmin()
adm.add_view(UserAdmin(User, db.session))
adm.add_view(ProductsAdmin(Product, db.session))
class IndexAdmin(AdminIndexView):
def is_accessible(self):
return current_user.is_authenticated and current_user.admin
def inaccessible_callback(self, name, **kwargs):
return redirect(url_for("views.home_page"))
class UserAdmin(ModelView):
form_excluded_columns = ["created_date", "updated_date", "products", "adresses"]
column_exclude_list = ["password", "phone", "money"]
column_searchable_list = ["email"]
def is_accessible(self):
return current_user.is_authenticated and current_user.admin
def inaccessible_callback(self, name, **kwargs):
return redirect(url_for("admin.index"))
class ProductsAdmin(ModelView):
form_excluded_columns = ["created_date", "updated_date", "user_products"]
column_exclude_list = ["image"]
column_type_formatters = {"image": FileField(label="Image", validators=[DataRequired()])}
def is_accessible(self):
return current_user.is_authenticated and current_user.admin
def inaccessible_callback(self, name, **kwargs):
return redirect(url_for("admin.index"))
I tried to do this to put the page restrict, but this isn't working...
class IndexAdmin(AdminIndexView):
def is_accessible(self):
return current_user.is_authenticated and current_user.admin
def inaccessible_callback(self, name, **kwargs):
return redirect(url_for("views.home_page"))
I'm not certain this is the gap, but the official docs do not suggest setting this as you are with adm.index_view =. Instead https://flask-admin.readthedocs.io/en/latest/api/mod_base/#default-view suggests:
admin = Admin(index_view=MyHomeView())
So, one possibility is that the init process for Admin() does something with the index_view that doesn't happen when you set it directly.
The docs (https://flask-admin.readthedocs.io/en/latest/api/mod_base/#flask_admin.base.Admin.init_app) also indicate that you can delay this by using init_app, which is the way I've used it before. Something like:
admin.init_app(app, index_view= MyHomeView())

Factory Boy test with Wagtail fails: TypeError: Field 'id' expected a number but got {}

I'm trying to set up Factory Boy for a Wagtail site. In order for that to work, the _create method must be able to establish a parent-child-relationship between pages. I override the _create method as described in the docs: https://factoryboy.readthedocs.io/en/latest/reference.html#attributes-and-methods. But the addition of the 'parent' key causes the error.
Have been looking at it for ages, dug into the source files. I suspect it has to do with the way the model_class method passes the kwargs, but I guess I'm blind for any obvious mistakes by now. Any help would greatly be appreciated!
models.py:
from wagtail.core.models import Page
class HomePage(Page):
pass
class SubjectPage(Page):
pass
tests.py:
from .factories import HomePageFactory, SubjectPageFactory
from django.test import TestCase
class TestModels(TestCase):
#classmethod
def setUpTestData(cls):
cls.homepage = HomePageFactory(title='Test page')
cls.subjectpage = SubjectPageFactory(parent=cls.homepage)
def test_dummy(self):
self.assertTrue(True)
factories.py:
import factory
from .models import HomePage, SubjectPage
from wagtail.core.models import Page
class PageFactory(factory.django.DjangoModelFactory):
class Meta:
abstract = True
#classmethod
def _create(cls, model_class, *args, **kwargs):
try:
parent = kwargs.pop('parent')
page = model_class(*args, kwargs)
except KeyError:
parent = Page.get_first_root_node()
page = model_class(*args, **kwargs)
parent.add_child(instance=page)
return page
class HomePageFactory(PageFactory):
class Meta:
model = HomePage
class SubjectPageFactory(PageFactory):
class Meta:
model = SubjectPage
Can I recommend wagtail_factories https://github.com/mvantellingen/wagtail-factories?
To set up:
pip install wagtail-factories
in factories.py
import wagtail_factories
from .models import HomePage, SubjectPage
class HomePageFactory(wagtail_factories.PageFactory):
class Meta:
model = HomePage
class SubjectPageFactory(wagtail_factories.PageFactory):
class Meta:
model = SubjectPage
in tests.py
from django.test import TestCase
class PagesTestCase(TestCase):
def test_create(self):
instance = HomePageFactory.create()
self.assertIsNotNone(instance.pk)
If you want to test pages with some structure, the code below would create a homepage as a root of the site, and create a subject page as a child of the homepage.
from wagtail.core.models import Site
from .factories import HomePageFactory, SubjectPageFactory
class PageTestCase(TestCase):
def setUp(self):
self.homepage = HomePageFactory.create()
self.site = Site.objects.all().first()
self.site.root_page = self.homepage
self.site.save()
self.site.refresh_from_db()
self.subject_page = SubjectPageFactory.create(parent=self.homepage)

django bulk user import user django-import-export

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

Django Admin linking to related objects

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.

django forms wizard and recaptcha

I did a recaptcha integration using the following django snippet
settings.py
RECAPTCHA_PUBLIC_KEY = '<your public key>'
RECAPTCHA_PRIVATE_KEY = '<your private key>'
#widgets.py
from django import forms
from django.utils.safestring import mark_safe
from django.conf import settings
from recaptcha import captcha
class ReCaptcha(forms.widgets.Widget):
recaptcha_challenge_name = 'recaptcha_challenge_field'
recaptcha_response_name = 'recaptcha_response_field'
def render(self, name, value, attrs=None):
return mark_safe(u'%s' % captcha.displayhtml(settings.RECAPTCHA_PUBLIC_KEY))
def value_from_datadict(self, data, files, name):
return [data.get(self.recaptcha_challenge_name, None),
data.get(self.recaptcha_response_name, None)]
#fields.py
from django.conf import settings
from django import forms
from django.utils.encoding import smart_unicode
from django.utils.translation import ugettext_lazy as _
from marcofucci_utils.widgets import ReCaptcha
from recaptcha import captcha
class ReCaptchaField(forms.CharField):
default_error_messages = {
'captcha_invalid': _(u'Invalid captcha')
}
def __init__(self, *args, **kwargs):
self.widget = ReCaptcha
self.required = True
super(ReCaptchaField, self).__init__(*args, **kwargs)
def clean(self, values):
super(ReCaptchaField, self).clean(values[1])
recaptcha_challenge_value = smart_unicode(values[0])
recaptcha_response_value = smart_unicode(values[1])
check_captcha = captcha.submit(recaptcha_challenge_value,
recaptcha_response_value, settings.RECAPTCHA_PRIVATE_KEY, {})
if not check_captcha.is_valid:
raise forms.util.ValidationError(self.error_messages['captcha_invalid'])
return values[0]
#forms.py
class RegistrationForm(forms.Form):
...
recaptcha = marcofucci_fields.ReCaptchaField()
...
But I have the forms defined in the django forms wizard and it calls the clean method on the field twice, even if the captcha is included in the last form.
As in the following:
from registration.forms import RegistrationWizard,RegistrationForm,ProfileForm
url(r'^register/$',
RegistrationWizard([RegistrationForm,ProfileForm]),
name='register_wizard'),
How do I circumvent this situation. What is the need to call the clean on the last form twice?
If a form is bound, it will allways call clean when renderering (even if you don't call form.is_valid).
You might want to consider using this instead:
django snippet 1644