Django: Global modification of readonly field appearance - python

I want to customize the field texture in django’s new “readonly mode”: E.g. foreign keys shall be displayed as links.
In general I identified the following options:
Implement custom fields for every model – results in code duplication
Reimplement django’s display_for_field method
Basically I could copy & paste the django.contrib.admin.utils module, insert my changes, override sys.modules['django.contrib.admin.utils'] = myutils but that's ugly because of maintainability in case of Django updates in the future.
So I decided to override only the display_for_fields method of django.contrib.admin.utils using the following approach to avoid duplication of Django code:
Override display_for_field function in django.contrib.admin.utils in settings.py:
from myapp.contrib.admin import utils
utils.override_method()
In myapp.utils.py:
from django.contrib.admin import utils
from django.contrib.admin.utils import *
def display_for_field_mod(value, field, empty_value_display):
if isinstance(field, models.ForeignKey) and value:
if field.related_model.__name__.lower() != 'user':
link_string = 'admin:myapp_' + field.related_model.__name__.lower() + '_change'
link = reverse(link_string, args=(value.id,))
return format_html('{}', link, value)
else:
return formats.localize(value)
else:
return display_for_field(value, field, empty_value_display)
def override_method():
utils.display_for_field = display_for_field_mod
But the problem is: display_for_field gets imported in django.contrib.admin.helpers using:
from django.contrib.admin.utils import (
display_for_field, [...]
)
So due to the scope of the imported funtion I cannot override this function from outside.
Do I miss some other obvious possibility? Is there a clean method to achieve this or is the only option to duplicate/modify django’s original code?

I came across a similar issue. If anyone's still looking for a solution, you just need to override the display_for_field in helpers, e.g.:
from django.contrib.admin import helpers, utils
def override_method():
helpers.display_for_field = display_for_field_mod
utils.display_for_field = display_for_field_mod
Only overriding helpers is sufficient, but it's a good idea to override utils as well, just in case.

Related

Running a system check after AppRegistry is initialized - Django Admin

I'm looking to run a specific check, to make sure that Django Admin classes (ModelAdmin, TabularInline etcetera) used within the project (within multiple apps) are using or inheriting from a class (a mixin in this case) - although a system check would fail since the AppRegistry is not yet loaded.
I'm using the below as of current; although this raises that the AppRegistry is not loaded.
from django.contrib.admin.sites import all_sites
from django.core.checks import register, Error
#register()
def check_django_admin_inheritance(app_configs, **kwargs):
errors = []
for admin_site in all_sites:
for model, admin in admin_site._registry.items():
if MyMixin not in admin.__class__.__bases__:
errors.append(
Error('All admin sites should derive from the MyMixin (common.django_admin)',
hint='Inherit from MyMixin or use our custom ModelAdmin (common.django_admin)',
obj=admin, id="id-here")
)
return errors
Is there any other way around this; besides the AppConfig.ready() which would require that I place it in every application - I'm more looking towards a solution that is clean and centralized.
You can simply register your check inside the AppConfig.ready() of some suitable app. Also you write errors.append([Error(...)]) which means you append a list of errors to the list you are supposed to return which will give you an error:
from django.contrib.admin.sites import all_sites
from django.core.checks import register, Error
def check_django_admin_inheritance(app_configs, **kwargs):
errors = []
for admin_site in all_sites:
for model, admin in admin_site._registry.items():
if MyMixin not in admin.__class__.__bases__:
errors.append(
Error('All admin sites should derive from the MyMixin (common.django_admin)',
hint='Inherit from MyMixin or use our custom ModelAdmin (common.django_admin)',
obj=admin, id="id-here")
)
return errors
class MyAppConfig(AppConfig):
...
def ready(self):
register(check_django_admin_inheritance) # Register the check here
I write this in my own app, and the check gives an error message for User and Group of the auth app, so this works as intended.

Best practice for defining default app settings for custom django module?

Is there a defined best practice for defining custom settings that come with a django app.
What I have at the moment is a separate file called app_settings.py that looks like this:
from django.conf import settings
# MY_SETTING_1 is required and will brake the application if not defined
try:
MY_SETTING_1 = str(getattr(settings, 'MY_SETTING_1'))
except AttributeError:
raise ImproperlyConfigured ('MY_SETTING_1 not defined in settings.py.')
# MY_SETTING_2 is not required and has a default value
MY_SETTING_2 = getattr(settings, 'MY_SETTING_2', ['default_value'])
Then I am importing this in my views.py, and using it like this:
from my_app import app_settings
print (app_settings.MY_SETTING_1)
print (app_settings.MY_SETTING_2)
This works ok, but I am not quite happy with the design. I am assuming that I can somehow use the class defined in apps.py, but I am not sure how.
Is there a best (or better) practice for this?
You could try https://pypi.org/project/typed_app_settings/.
Example:
# my_app/app_settings.py
from typed_app_settings import UndefinedValue, typed_app_settings_dict
#typed_app_settings_dict("MY_APP")
class Settings:
MY_SETTING_1: str = UndefinedValue()
MY_SETTING_2: str = "default_value"
settings = Settings()
Then in the view you would just use it like this.
from my_app.app_settings import settings
print(app_settings.MY_SETTING_1)
print(app_settings.MY_SETTING_2)

Expiring a view-cache in Django 1.3.1

I'm trying to expire a view-level cache on a model's post_save (that was set via https://docs.djangoproject.com/en/1.3/topics/cache/?from=olddocs#the-per-view-cache). I did some googling and found this answer here on SO: Expire a view-cache in Django? but it's not working for me.
I asked around in the #django room on freenode and the consensus was that this was probably due to the recent caching changes made in 1.3
Does anyone have any idea on how I can wipe out the cache entry for a model keyed off it's get_absolute_url()?
I figured it out!
Cheers to ilvar for pointing me in the right direction. My implementation is below. I created a property named cache_key and added a post_save receiver onto the sub class of the models whose view-level caches I needed to clear after they had been updated. Suggestions for improvement are always welcome!
from django.conf import settings
from django.core.cache import cache
from django.db.models.signals import post_save
from django.http import HttpRequest
from django.utils.cache import _generate_cache_header_key
from someapp.models import BaseModelofThisClass
class SomeModel(BaseModelofThisClass):
...
#property
def cache_key(self):
# Create a fake request object
request = HttpRequest()
# Set the request method
request.method = "GET"
# Set the request path to be this object's permalink.
request.path = self.get_absolute_url()
# Grab the key prefix (if it exists) from settings
key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
# Generate this object's cache key using django's key generator
key = _generate_cache_header_key(key_prefix, request)
# This is a bit hacky but necessary as I don't know how to do it
# properly otherwise. While generating a cache header key, django
# uses the language of the HttpRequest() object as part of the
# string. The fake request object, by default, uses
# settings.LANGUAGE_CODE as it's language (in my case, 'en-us')
# while the true request objects that are used in building views
# use settings.LANGUAGES ('en'). Instead of replacing the segment
# of the string after the fact it would be far better create a more
# closely mirrored HttpRequest() object prior to passing it to
# _generate_cache_header_key().
key = key.replace(settings.LANGUAGE_CODE, settings.LANGUAGES[settings.DEFAULT_LANGUAGE][0])
return key
#receiver(post_save)
def clear_cache_for_this_item(sender, instance, **kwargs):
# If this is a sub-class of another model
if sender not in BaseModelofThisClass.__subclasses__():
return
else:
cache.delete(instance.cache_key)
Django's caching middleware is using this to generate a cache key for the request. So, you can make a fake request with desired path and get a cache key. Then simply delete it from the cache.
P.S. cache_page decorator also uses that middlewares so it should work the same way.

piston-django how many methods should a single handler contain

I've been building a handler class for each method I want to map to the url file. Is my approach correct or wrong? because I don't seem to find a way to map a resource to a method they all map to an entire class.
Regards,
The documentations seems very clear https://bitbucket.org/jespern/django-piston/wiki/Documentation#!resources
from piston.handler import BaseHandler
from myapp.models import Blogpost
class BlogpostHandler(BaseHandler):
allowed_methods = ('GET',)
model = Blogpost
def read(self, request, post_slug):
...
Piston lets you map resource to
models, and by doing so, it will do a
lot of the heavy lifting for you.
A resource can be just a class, but
usually you would want to define at
least 1 of 4 methods:
read is called on GET requests, and
should never modify data (idempotent.)
create is called on POST, and creates
new objects, and should return them
(or rc.CREATED.)
update is called on PUT, and should
update an existing product and return
them (or rc.ALL_OK.)
delete is called on DELETE, and should
delete an existing object. Should not
return anything, just rc.DELETED.
Also https://bitbucket.org/jespern/django-piston/wiki/Documentation#!mapping-urls
In urls.py:
from django.conf.urls.defaults import *
from piston.resource import Resource
from mysite.myapp.api.handlers import BlogpostHandler
blogpost_handler = Resource(BlogpostHandler)
urlpatterns = patterns('',
url(r'^blogpost/(?P<post_slug>[^/]+)/', blogpost_handler),
url(r'^blogposts/', blogpost_handler),
)

Help with Admin forms validation error

I am quite new to Django, I'm having few problems with validation
forms in Admin module, more specifically with raising exceptions in the
ModelForm. I can validate and manipulate data in clean methods but
cannot seem to raise any errors. Whenever I include any raise
statement I get this error "'NoneType' object has no attribute
'ValidationError'". When I remove the raise part everything works
fine.
Then if I reimport django.forms (inside clean method) with a different alias (e.g. from django import forms as blahbalh) then I'm able to raise messages using blahblah.ValidateException.
Any tips or suggestions on doing such a thing properly ?
Here's an example of what I'm doing in Admin.py:
admin.py
from django import forms
from proj.models import *
from django.contrib import admin
class FontAdminForm(forms.ModelForm):
class Meta:
model = Font
def clean_name(self):
return self.cleaned_data["name"].upper()
def clean_description(self):
desc = self.cleaned_data['description']
if desc and if len(desc) < 10:
raise forms.ValidationError('Description is too short.')
return desc
class FontAdmin(admin.ModelAdmin):
form = FontAdminForm
list_display = ['name', 'description']
admin.site.register(Font, FontAdmin)
--
Thanks,
A
You problem might be in the * import.
from proj.models import *
if proj.models contains any variable named forms (including some module import like "from django import forms), it could trounce your initial import of:
from django import forms
I would explicitly import from proj.models, e.g.
from proj.models import Font
If that doesn't work, see if there are any other variables name "forms" that could be messing with your scope.
You can use introspection to see what "forms" is. Inside your clean_description method:
print forms.__package__
My guess is it is not going to be "django" (or will return an error, indicating that it is definitely not django.forms).

Categories

Resources