Should I define get_absolute_url() if the url is not unique? - django

In the Django framework, models can define a get_absolute_url() method. The method is used to create urls for instances of the model and it is considered a good practice to define and use this method.
Is it still a good practice to define this method even if the generated urls are not unique?
Example:
class Project(models.Model):
name = models.CharField(max_length=128)
def get_absolute_url(self):
return reverse('xxx:project_details', args=(self.id,))
class Item(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
name = models.CharField(max_length=128)
def get_absolute_url(self):
return reverse('xxx:project_details', args=(self.project.id,))
Currently, the Item instances can only be seen in a list on the project_details page and I intend to keep it that way. The get_absolute_url() method returns the project details url. This means that all Items of the same project return the same url. Is that okay? I found it useful, because some generic views use get_absolute_url() and automatically redirect to the correct page. However, I am new to Django and want to know whether this will cause problems later.

Short answer: I would advice not to do this. Usually the get_absolute_url links to a "unique identifier" for that object.
Is it still a good practice to define this method even if the generated urls are not unique?
The documentation on get_absolute_url(..) [Django-doc] mentions that:
Define a get_absolute_url() method to tell Django how to calculate
the canonical URL for an object. To callers, this method should
appear to return a string that can be used to refer to the object over
HTTP.
(...)
Similarly, a couple of other bits of Django, such as the syndication
feed framework, use get_absolute_url() when it is defined. If it
makes sense for your model’s instances to each have a unique URL,
you should define get_absolute_url().
In mathematics and computer science a canonical form [wiki] says that:
(...) The distinction between "canonical" and "normal" forms varies by subfield. In most fields, a canonical form specifies a unique representation for every object, while a normal form simply specifies its form, without the requirement of uniqueness. (...)
So this hints that get_absolute_url makes more sense if the URLs that are generated are unique. After all, the get_absolute_url aims to show the details of that specific object. You use it to redirect(some_object), etc.
Although it will of course not raise errors, it is not very common to use get_absolute_url to link to the detail page of a parent object.
If you proceed, you might implement it as: return self.project.get_absolute_url(..) instead of implementing the same reverse(..) logic, since if you later alter the get_absolute_url of the project, then the get_absolute_url of item will be updated as well.

Related

Difference between using url tag and get_absolute_url

If I have a model like this:
class Article(models.Model):
title = models.CharField(max_length=200)
# ... rest of the code ...
def get_absolute_url(self):
return reverse('article-detail', args=[str(self.pk)])
and I have an url mapping like this:
url(r'^article/(?P<pk>[0-9]+)/$', views.ArticleView.as_view(), name='article-detail'),
In template should I use:
{{ article.title }}
or
{{ article.title }}
I'm still thinking both are good ideas, but which is the best?
In the first code I've written args=[str(self.pk)], why I must convert self.pk into string? URLs must be strings?
In my generic view, how do I use pk variable?
I'm really confused with that slug_field, slug_url_kwarg, pk_url_kwarg, query_pk_and_slug.
Which matches which?
If I set query_pk_and_slug to True, slug_field = pk?
In my opinion,use
{{ article.title }}
is better practice.
If later on you want to change the url of this resource, you will do it once in your models function, and you would not search every template page for reference to this specific url.
The philosophy behind this, is that the url for an article is a resource that belongs to the model of the article (django: Fat models and skinny controllers?)
A better approach, is to write
return reverse('article-detail', kwargs={'pk': self.pk})
This way, and when having multiple args in your url, you know every time the value of each arg (*args and **kwargs?)
I am not sure about the last part of your question. All in all, pk represents the primary key, which by default (and leave it as it) is the id (automatically produced by your database), and slug is a unique field in database (you specify it in your model definition) that represents a SlugField. Slug is used when you prefer more readable (seo) urls like /article/giannis-antetokounmpo-is-the-best, instead of /article/404.
For understanding how the class-based views work in django (better practice than function-based) take a look https://ccbv.co.uk/projects/Django/1.10/django.views.generic.detail/DetailView/ for example.
When the GET (http method is called), the get function of the model is called as a result. If you notice, there is a self.get_object() function. In the definition of the get_object(), you can see the logic you are searching for. Specifically, in the comments, you can see all the ways, that View is trying to find the one and only object to return. You must choose one, by specifying the appropriate variables.
url tag will do the reverse operation and generate a url path, where as get_absolute_url should be defined in a model as this is to get url for a particular object.
Here is a post explaining get_absolute_url

Django: How to set up a single view for multiple urls where each url points to subclass of a base model?

I have the following urls:
browse/college
browse/department
browse/subjects
I have Tag model where College, Department and Subject are all subclasses on Tag. All the urls will call a single view.
I want to create a view called browse_specfic_tag(request, model_name)
I was thinking of converting model name to model using get_model and do something like,
TagModel = get_model(model_name..)
but I am going to tie url name with model which might not be a good thing to do if I decided to rename either of them.
Any better way to do this?
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
…
The proper way of solving this is passing an extra option to the view. See related documentation entry.
Example:
url('^college/$', 'tag_view', {'model': College})
def tag_view(request, model):
records = model.objects.filter(…)
Furthermore, actions should not be included in URL's name. URLs should identify resources. Therefore I would skip the browse part of the URL.

Get an url pk in a generic RESTful view?

I'm trying to manage my REST API like that :
http://xxx/users/userid[0-9]+/projects/projectid[0-9]+/tasks/taskid[0-9]+/
So I can access the JSON easily in my website. But, the thing is, I defined my view classes using the REST framework generic views. For example, here is my UserDetail view :
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
But of course I don't want all my users to be displayed, I just want my user with the ID userid to be displayed. I don't really know how to do it, I tried
queryset = User.objects.filter(id=userid)
but of course userid is not defined... Any help please ?
Edit : just to clarify, here is the url that leads to this view :
url(r'^users/(?P<pku>[0-9]+)/$', views.UserDetail.as_view(
), name='user-detail'),
First of all, If you want to use class based views, you should read some more about them. They are thoroughly explained on Django's docs and you can read about the specific generics of the framework you're using on the REST framework docs too. I'm not saying that you haven't read those, just that you seem to be missing some basic concepts that are explained there.
Now, to the problem at hand, if you look at the doc's of the generic view you're extending, you can see that it represents the endpoints for a single instance, meaning that it won't act on all your model's instances (as you seem to assume).
Also, you can see that this view is built on top of a series of other classes, the more critical one being GenericAPIView. On there you can see two things:
The queryset class field in this context is meant for filtering the posible instances you can manipulate, not obtaining the specific instance described on your url.
The lookup_field is the field that defines which attribute from your model will be used for getting the actual instance. You should define this field to whatever field you're going to use on your url to identify your object (generally it's pk). It's also important to note that the url should include a keyword argument corresponding to this value.
Internally, the view will take care of calling the get_object method, which usese the lookup_field value to find the specific model, and then feed that object to the serializer and return the result back to the client.
DISCLAIMER: I've never used Django REST framework, I put this answer togheter by reading the relevants docs and based on my experience.
I guess you need this:
Resolve function (django 1.4)
Then in your view class method you can do:
temp1, args, kwargs = resolve(self.request.path)

how to find associated Django ModelForm given the Model

I have dozens of Models, each with ONE associated ModelForm (whose Meta.model refers to the Model in question).
E.g.
class FooModel(Model):
pass
class FooModelForm(ModelForm):
class Meta:
model = FooModel
# current approach using a classmethod
FooModelForm.insert_in_model() # does cls.Meta.model.form = cls
So, obviously, it's easy to find FooModel given FooModelForm. What I want is to know the best way to do the REVERSE: find FooModelForm when I am presented with FooModel or even the string "Foo".
Assume only one ModelForm for each model, although solutions that return multiple are fine.
My current approach is to stash the model in the form class (as shown above), but I'm interested in knowing better approaches especially ones that could compute it centrally (without the final line above).
EDIT: I've reviewed things like Django: Display Generic ModelForm or predefined form but I believe this is a simpler question than those. The Django admin code must do something along the lines of what I seek. But get_model equivalent for ModelForms? suggests that might be voodoo and that it would be best to just do dict['Foo']=FooModelForm or its equivalent to keep track of the association explicitly. Seems repetitious.
If you have under 20 forms, sounds like mapping out a dictionary is the easiest way. Django does this kinda thing internally too.
For ModelForms, django admin just creates them on the fly via modelform_factory, so there is no comparable method to get_model
I do see, your method is bullet proof, but requires a line in ever model def.
If you only have one ModelForm per model, you could potentially iterate through the ModelForm subclasses until you find your form.
find FooModelForm when I am presented
with FooModel or even the string
"Foo".
modelforms = forms.ModelForm.__subclasses__()
def get_modelform(model):
try:
return filter(lambda x:x.Meta.model == model, modelforms)[0]
except IndexError:
print "apparently, there wasn't a ModelForm for your model"
If you want to pull the ModelForm as a string, you'll need to make sure both
app_label and __name__ are correct, which means it will be easier to use get_model('app', 'model') in the function.
You could combine this with your method and automatically place an attribute on your models that point to its ModelForm.
Hook into the class_prepared signal at the top of your apps, find the corresponding ModelForm and attach it to your Model class.
Hope that helps or gives you some ideas.

Making a fairly complex Django model method sortable in admin?

I have a reasonably complex custom Django model method. It's visible in the admin interface, and I would now like to make it sortable in the admin interface too.
I've added admin_order_field as recommended in this previous question, but I don't fully understand what else I need to do.
class Book(models.Model):
id = models.IntegerField(primary_key=True)
title = models.CharField(max_length=200)
library_id = models.CharField(max_length=200, unique=True)
def current_owner(self):
latest_transaction = Transaction.objects.filter(book=self)[:1]
if latest_transaction:
if latest_transaction[0].transaction_type==0:
return latest_transaction[0].user.windows_id
return None
current_owner.admin_order_field = 'current_owner'
Currently, when I click on the current_owner field in the admin interface, Django gives me
FieldError at /admin/books/book/
Cannot resolve keyword 'current_owner' into field
Do I need to make a BookManager too? If so, what code should I use? This isn't a simple Count like the example in the previous question, so help would be appreciated :)
Thanks!
The Django admin won't order models by the result of a method or any other property that isn't a model field (i.e. a database column). The ordering must be done in the database query, to keep things simple and efficient.
The purpose of admin_order_field is to equate the ordering of a non-field property to the ordering of something that is a field.
For example, a valid values current_owner.admin_order_field could be id, title or library_id. Obviously none of these makes sense for your purpose.
One solution would be to denormalise and always store current_owner as a model field on Book; this could be done automatically using a signal.
You can't do this. admin_order_field has to be a field, not a method - it's meant for when you have a method that returns a custom representation of an underlying field, not when you do dynamic calculations to provide the value. Django's admin uses the ORM for sorting, and that can't sort on custom methods.