Lookup admin change url for arbitrary Django model - python

How do you lookup the admin change url for an arbitrary model?
If I know the model, I can get the url by doing something like:
>>> print urlresolvers.reverse('admin:myapp_mymodel_change', args=(obj.id,))
/admin/myapp/mymodel/123/
I have a generic foreign key on a model, and I'd like to provide a link in admin to the object's corresponding change page. Since it can be any type of model, I can't easily use reverse(). Is there some way I could simply this to the following?
>>> get_admin_change_url(obj)
/admin/myapp/mymodel/123/

Once you have the object, you can access its app label and name on its _meta class, then construct the name of the admin change url dynamically.
app_label = obj._meta.app_label
model = obj._meta.module_name
reverse('admin:%s_%s_change' % (app_label, model), args=(obj.id,))

Related

Flask Admin Get Instance of Model in ModelView Custom Template

I'm currently trying to create a custom ModelView for a detail_view using a template on Flask Admin. However I'm struggling to figure out how to access the instance of a model that the user is viewing the details of.
This is my custom ModelView:
class ShopModelView(MyModelView):
can_view_details = True
details_template = "custom_detail_view.html"
#expose('/details/', methods=('GET', 'POST'))
def details_view(self):
self._template_args['all_transactions'] = #current_shop_object#.transactions.order_by(Transaction.timestamp.desc())
return super(ShopModelView, self).details_view()
Looking at this post, the class of the model can be obtained using self.model, however this returns the class rather than the instance of the specific model being accessed.
The documentation on Templates and ModelView doesn't seem to explain it.
How can I get the instance of the specific model being accessed?
Turns out that the current instance of the model can be accessed via the variable model inside the jinja2 template file. So instead of parsing the variable as a template argument like I was trying to: self._template_args['all_transactions'] = #current_shop_object#.transactions.order_by(Transaction.timestamp.desc()),
{{ model.transactions.all() }} achieves the result I was aiming for.

Django admin UserChangeForm with UUID field as primary key

I'm working on a Django app that will contain sensitive User data, so I'm taking a number of steps to anonymise User objects and their data.
I created custom 'User' object that subclassses the AbstractBaseUser model like so:
class User(AbstractBaseUser, PermissionsMixin):
(...)
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
(...)
It has the following linked ModelAdmin object:
from django.contrib.auth.forms import UserChangeForm
#admin.register(User)
class UserAdmin(admin.ModelAdmin):
form = UserChangeForm
I'm using UUID fields for primary keys to ensure maximum anonymity, yet I would like to be able to reset User passwords in the Django Admin (like you can do with the default User object)
However, when I open a User in the admin and press the link to change the password I get the following error message:
User with ID "21362aca-6918-47ea-9b29-275350a89c54/password" doesn't exist. Perhaps it was deleted?
The admin url is still expecting a url with the an integer as its pk value.
So it seems that I have to override the admin url configuration in the ModelAdmin definition, but I was wondering if there was a simpler way to achieve the desired result - as I imagine that replacing the User.pk with an UUID field is a fairly regular occurrence and I image many developers have ran into this problem. I tried to find some kind of settings / toggle to achieve this but to no avail, am I missing something?
Your 'UserAdmin' inherits from ModelAdmin, which provides the urls via the get_urls method for the add, change, delete, etc. views but also a 'fallback' url:
urlpatterns = [
#...
url(r'^(.+)/change/$', wrap(self.change_view), name='%s_%s_change' % info),
# For backwards compatibility (was the change url before 1.9)
path('<path:object_id>/', wrap(RedirectView.as_view(
pattern_name='%s:%s_%s_change' % ((self.admin_site.name,) + info)
))),
]
The url you are following looks like /user/<UUID>/password/ which only fits the regex of the fallback pattern - which redirects you to the change page in such a way that it uses <UUID>/password as the object_id.
Try inheriting from django.contrib.auth.admin.UserAdmin instead as its get_urls method provides the url pattern you need.
Some more poking around...
If your primary key field was an AutoField, the whole process would raise a ValueError('invalid literal for int') when trying to cast int('some_integer/password') in django.db.models.fields.AutoField.get_prep_value in order to prepare the value for a query. Understandable!
HOWEVER: UUIDField uses the get_prep_value method of the base class Field. Field.get_prep_value just simply returns the value without even checking (after all, validating the value should be the job of UUIDField). So you end up with a query that looks for the bogus uuid '<uuid>/password', which obviously doesn't exist.

How does Django know what 'pk' to use in urls.py?

I've been learning Django over the past couple of weeks, and there is one thing that really seems to confuse me. Which model's attributes does Django use to define the <pk> that is used in urls.py?
For example, If I have:
urlpatterns = [
url(r'^(?P<pk>\d+)/$', ProductDetailView.as_view(), name="product-detail"),
]
I had assumed previously that the pk would be derived from the model instance that is being used in the given view, in this case, ProductDetailView.as_view(). However, I'm starting to question that logic as you can pass multiple models into a view, of course.
Part 2
Also, what if I wanted to use the pk of one model instance, while only using a different model instance in the view?
For example, what if I had two models, Products & Stores which both hold a many-to-many relationship (eg. a product could be in multiple stores, and a store can hold many products). Then I wanted to have a url where I have a StoreListView listing all the stores that hold a given product, so my url would be something like:
url(r'^(?P<pk>\d+)/$', StoreListView.as_view(), name="store-list")
Where the pk is of the Product instance but the view is of the Store instance
To finalize the question, again, how does Django define pk?
Django doesn't define anything. You define it. ProductDetailView must have a model attribute; it is that attribute that defines what model to use.
Unfortunately, part 2 of your question doesn't really make sense; a view is not an instance of a model.
Have a look at the various methods and attributes of the detail view here, particularly the get_object method:
http://ccbv.co.uk/projects/Django/1.9/django.views.generic.detail/DetailView
def get_object(self, queryset=None):
"""
Returns the object the view is displaying.
By default this requires `self.queryset` and a `pk` or `slug` argument
in the URLconf, but subclasses can override this to return any object.
"""
# Use a custom queryset if provided; this is required for subclasses
# like DateDetailView
if queryset is None:
queryset = self.get_queryset()
# Next, try looking up by primary key.
pk = self.kwargs.get(self.pk_url_kwarg)
slug = self.kwargs.get(self.slug_url_kwarg)
if pk is not None:
queryset = queryset.filter(pk=pk)
# Next, try looking up by slug.
if slug is not None and (pk is None or self.query_pk_and_slug):
slug_field = self.get_slug_field()
queryset = queryset.filter(**{slug_field: slug})
# If none of those are defined, it's an error.
if pk is None and slug is None:
raise AttributeError("Generic detail view %s must be called with "
"either an object pk or a slug."
% self.__class__.__name__)
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
It's Django, so its fairly customizable, so it doesn't need to be called 'PK'. You could override that by using the pk_url_kwarg. By default the id field is the pk, unless you specify it in your model definition.
Django's DetailView uses the SingleObjectMixin mixin which has a method called get_object which will look for a pk_url_kwarg
pk = self.kwargs.get(self.pk_url_kwarg)
By default this is set to 'pk'

How to reference a ModelView in flask-admin

What's the right way to get the URL for a flask-admin ModelView?
Here's a very simple example:
my_admin_view.py
from flask.ext.admin.contrib.sqla import ModelView
from common.flask_app import app
from models import db, User, Role
admin = Admin(app, name="Boost Admin")
admin.add_view(ModelView(User, db.session, category="model"))
admin.add_view(ModelView(Role, db.session, category="model"))
my_admin_template.html
...
<p>Check out my user admin link:</p>
User view link
{# ______________
what argument to pass in here?? #}
...
What's the correct argument to pass to url_for(...)?
I've tried modelview.user, my_admin_view.modelview.user, etc. None of them seem to resolve correctly, and I'd like to avoid hardcoding the link.
thanks!
OK I figured it out after reading the source code for ModelView.
First, make sure that endpoints are named (it's possible to do it without named endpoints, but this makes the code much clearer):
from flask.ext.admin.contrib.sqla import ModelView
from models import db, User, Role
admin = Admin(app, name="Boost Admin")
admin.add_view(ModelView(User,db.session,category="model", endpoint="model_view_user"))
admin.add_view(ModelView(Role,db.session,category="model", endpoint="model_view_role"))
...now in the template, you can reference the basic model view as follows:
URL for User model default view is: {{model_view_user.index_view}}
URL for Role model default view is: {{model_view_role.index_view}}
The index_view function is defined here, and implements the default view for a flask admin ModelView.
See the section Generating URLs in the Flask-Admin introduction.
It says to "use the lowercase name of the model as the prefix". Add a dot, and the name of the view.
index_view for the overview list.
create_view for creating a new row.
edit_view for modifying an existing row.
So you can easily do:
url_for('user.index_view')
url_for('role.create_view')
url_for('user.edit_view', id=1)
It should be
url_for('admin.user')
If you read the flask-admin docs here, for generating URLs, it clearly says:
If you want to generate a URL for a particular view method from outside, the following rules apply:
....
3. For model-based views the rules differ - the model class name should be used if an endpoint name is not provided.

Accessing form fields as properties in a django view

According to the Django tutorial, you should access form fields using cleaned_data dictionary. I'm wondering why I can't access the properties of the form directly? My form validates just fine, but when I try to access it, Django complains that the object does not have the attribute. I added some code below that I hope will help diagnose the problem.
Form:
class CustomForm(forms.Form):
description = forms.CharField(widget = forms.TextInput(attrs = {'placeholder' : 'enter some text'}), label = "My form")
View:
def process_form(request):
if request.method != 'POST':
raise Http404
myForm = CustomForm(request.POST)
if not myForm.is_valid():
c = RequestContext(request)
return render_to_response('home/index.html', {'form' : myForm }, c)
# debug
print 'Description: ' + myForm.description # this does NOT work
# print 'Description: ' + myForm.cleaned_data['description'] # this does work
I get the following error: 'CustomForm' object has no attribute 'description'. Did I miss something in the docs that says I can't do that?
If your form is validated then you can access myForm cleaned_data:
print myForm.cleaned_data.get('description')
If you want to see why you cannot access myForm.description then you can see the data dictionary of your myForm:
print myForm.__dict__
The way you define fields using django.forms is just a convenient, declarative syntax; it's not really representative of what the final Form class, or an instance of it, looks like in terms of attributes.
Forms have a metaclass (without getting too deep into it, a metaclass is to declaring a class using the class keyword as an __init__ method is to creating an instance of a class using parentheses -- a hook to customise the object being created, which in the case of a metaclass, is a class!) which picks off Fields from the form class at definition time and adds them to a base_fields dict. When you instantiate a form, its base_fields are deep-copied to a fields attribute on the instance.
One point of confusion might be that you use . to access fields for display in templates -- what's actually happening there is that Django's template engine first attempts to use dictionary-style [] access to resolve property lookups and the base form class defines a __getitem__ method to take advantage of this, looking up the appropriate field from the form instance's fields dict and wrapping it with a BoundField, a wrapper which knows how to use the field and data from the form for displaying the field.
You can access the fields of a Form instance from its fields attribute.
myForm.fields['description']
And some property like label can be accessed like this:
myForm.fields['description'].label
Not sure how to display the value corresponding. Anybody having idea?
here is my reference
https://docs.djangoproject.com/en/dev/ref/forms/api/#accessing-the-fields-from-the-form
You can access your field trought dict.
form.__dict__["fields"]["description"]

Categories

Resources