I was following this tutorial
Here is the model:
class Post(models.Model):
title = models.CharField(max_length=200)
pub_date = models.DateTimeField()
text = models.TextField()
slug = models.SlugField(max_length=40, unique=True)
def get_absolute_url(self):
return "/{}/{}/{}/".format(self.pub_date.year, self.pub_date.month, self.slug)
def __unicode__(self):
return self.title
class Meta:
ordering = ['-pub_date']
Here is the url for accessing a post:
from django.conf.urls import patterns, url
from django.views.generic import ListView, DetailView
from blogengine.models import Post
urlpatterns = patterns('',
url(r'^(?P<pub_date__year>\d{4})/(?P<pub_date__month>\d{1,2})/(?P<slug>[a-zA-Z0-9-]+)/?$', DetailView.as_view(
model=Post,
)),
)
What's interesting about this code is the use of double underscore in the url pattern. I did lookup django's documentation on url pattern: https://docs.djangoproject.com/en/1.8/topics/http/urls/ But I cannot find any docs on using double underscore inside url pattern.
My guess about this usage is that a keyword argument called pub_year will be passed to the view function of DetailView, and the pub_year argument has two attributes year and month.
I tried to use the following url and it still worked:
url(r'^(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<slug>[a-zA-Z0-9-]+)/?$', DetailView.as_view(
model=Post,
)),
So I guess the use of double underscore is not necessary.
I found this line in Django's source code
It looks like a detailview (which inherits from SingleObjectMixin) can use slug to match a record. If that is the case, then the year and month arguments are not needed.
So here are my questions:
Is there any value in using double underscore in url pattern?
When I reduce the url pattern to the following, I only get a 404 when requesting the page with: 127.0.0.1:8000/test/ (test is the slug for an existing record stored in db)
url(r'^/(?P<slug>[a-zA-Z0-9-]+)/?$', DetailView.as_view(
model=Post,
)),
why is that?
Since this has not really been answered, yet:
Is there any value in using double underscore in url pattern?
DRY, more or less:
class SomeDetailView(...):
def get_queryset(self):
queryset = super(SomeDetailView, self).get_queryset()
# grab all named URL keyword arguments and
# transform them into field lookup patterns.
# Leave out slug and primary key keywords, though.
filter_args = {
k: v for k, v in self.kwargs.iteritems()
if k not in (self.slug_url_kwarg, self.pk_url_kwarg)
}
if filter_args:
queryset = queryset.filter(**filter_args)
return queryset
Since e.g. pub_date__year is a valid field lookup you --possible security problems nonwithstanding-- just gained the ability to add lookup criteria solely via named capture patterns in urls.py.
When I reduce the url pattern to the following, I only get a 404 when requesting the page with: 127.0.0.1:8000/test/ (test is the slug for an existing record stored in db)
url(r'^/(?P<slug>[a-zA-Z0-9-]+)/?$', DetailView.as_view(model=Post, )),
^ leading slash
That's common enough mistake that it made it into documentation:
There’s no need to add a leading slash, because every URL has that. For example, it’s ^articles, not ^/articles.
Try again with r'^(?P<slug>[a-zA-Z0-9-]+)/?$'
Documentation is, however, a little misleading, here. "There's no need" should be read as "The initial leading slash is matched automatically and is not part of the url pattern".
Those ?Ps are just capturing patterns in a regex; they are names and can be whatever you want, but will be passed as keyword arguments. They need a <name> (ex (?P<name>) ) or they will be passed as regular arguments, which is why your reduced example failed.
Personally I reserve __s for db lookups, and would use something like (?<pub_year>) as a more descriptive variable name.
There are default conventions in class-based-views that let you use standard names to write really terse code. In DetailView, 'slug' is the default slug_field and slug_url_kwarg; if you trace the dispatch() pattern through, you will see where those defaults can be tweaked/how they are used. BTW: CCBV is really useful and you should use it, and your tutorial link appears broken.
Related
I wrote this code that dynamically generates url patterns from the database. These urls have only one level path: domain.com/something.
someapp/models.py
class SomeModel(models.Model):
pattern = models.CharField(max_length=50)
name = models.CharField(max_length=50)
text = models.CharField(max_length=50)
someapp/apps.py
class SomeAppConfig(AppConfig):
name = 'someapp'
def ready(self):
from .models import SomeModel
from .urls import urlpatterns
from . import views
urls_in_db = SomeModel.objects.all()
for url_in_db in urls_in_db:
urlpatterns.append(path(url_in_db.pattern,
views.SpecialView.as_view(),
name=url_in_db.name)
someapp/views.py
class SpecialView(generic.TemplateView):
template_name = 'template/view_template.html'
model = SomeModel
def get_context_data(self, **kwargs):
context = super(SpecialView, self).get_context_data(**kwargs)
context['content'] = SomeModel.objects.get(pattern=self.request.path)
return context
Is this solution an anti-pattern? And, if so, why?
Thanks
Yes, your solution is an anti-pattern. Django supports parameters in URL patterns that get captured and become available in the corresponding view. Using these URL parameters, you can write and maintain a single URL pattern for every record of a certain type in your database.
Take a look at this example of URL parameters.
Finally, also note how your solution could potentially have very poor performance since you are potentially creating millions of URL patterns depending on the size of your database.
I have a django application 'test_app'. The test_app page - "domain/test_app/" has a link to another page within the app "Genes". This takes me to "domain/test_app/genes/". In this page I have a list of links which comes from my a table in my django database called "gene", which is in test_app/models.py as class Gene. This links to another table called "Variants" via attribute 'gene' as a foreign key.
This is all in test_app/models.py:
class Gene(models.Model):
gene = models.CharField(primary_key=True, max_length=110)
transcript = models.CharField(max_length=100)
def __str__(self):
return self.gene
class Variant(models.Model):
variant = models.CharField(primary_key=True, max_length=210)
gene = models.ForeignKey(Gene, on_delete=models.CASCADE)
cds = models.CharField(max_length=150)
def __str__(self):
return self.variant
My test_app/urls.py urlpatterns look like this:
urlpatterns = [
url(r'^$', views.test_app, name='test_app'),
url(r'^genes/$', views.genes, name='genes'),
url(r'^genes/(?P<gene>)', views.variant_info, name='variant_info'),
]
and test_app/views.py look like this (link to test_app/templates/test_app/ directory with my templates in):
def test_app(request):
template = loader.get_template('test_app/index.html')
return render(request, 'test_app/index.html')
def genes(request):
gene_list = Gene.objects.all()
context = {'gene_list': gene_list}
return render(request, 'test_app/genes.html', context)
def variant_info(request, gene):
variant_info = Variant.objects.filter(gene=gene)
return(request, 'test_app/gene_info.html', {'variant_info': variant_info}
I have a list of genes in my gene table in my database. When obtaining these genes using 'gene_list = Gene.objects.all()' this works fine. I then render this list to a template as a series of links on my "domain/test_app/genes" page. For example, when I click on TP53 on this page, it goes to "domain/test_app/genes/TP53/". This takes me to the correct page.
However, I clearly do not have the regex for this urlpattern correct as when I type in more digits or characters such as /TP53098120918 it still goes to the same page. Even when I put r'^genes/(?P)$' this is still happening and I dont understand why?
But the main problem I am having is obtaining the information from my variant table in my database and rendering this to the specific gene page, such as domain/test_app/genes/TP53.
When I am clicking on the TP53 it does not pass through to my view function 'variant_info' as the gene argument because the line:
variant_info = Variant.objects.filter(gene=gene)
does not obtain any information from the database - i thought this would pass gene='TP53' when I click on the link, and therefore this would be:
variant_info = Variant.objects.filter(gene='TP53')
which would return the information from the variant table with the gene as TP53. I don't know how to properly access the TP53 from the genes function in views.py.
The problem is that your (?P<gene>) is not matching any characters, so you will always have gene='' in your view.
Try changing your url pattern to:
url(r'^genes/(?P<gene>\w+)/$', views.variant_info, name='variant_info'),
This will accept letters A-Z, a-z, digits 0-9 and underscore for the gene argument. I've added the trailing slash for consistency with your other views, and the dollar because it's usually a good idea to do this.
for the url you'd need something like to match only digits at the end
r'^genes/(?P<gene>TP\d+)$'
(I think otherwise it will try to match a string and just accept the first match)
as for your second question: sorry, but I don't get what you're asking.
But looking at your code I assume you want to filter by properties of the foreign Model:
variant_info = Variant.objects.filter(gene__gene__contains=gene)
This means to search in field gene of object Gene.
ps. that starts to look confusing, I would suggest changing the model to something like
class Gene(models.Model):
name = models.CharField(primary_key=True, max_length=110)
...
I have the following URL in my urlpatterns:
url(r'^user/(?P<user_pk>[0-9]+)/device/(?P<uid>[0-9a-fA-F\-]+)$', views.UserDeviceDetailView.as_view(), name='user-device-detail'),
notice it has two fields: user_pk, and uid. The URL would look something like: https://example.com/user/410/device/c7bda191-f485-4531-a2a7-37e18c2a252c.
In the detail view for this model, I'm trying to populate a url field that will contain the link back to the model.
In the serializer, I have:
url = serializers.HyperlinkedIdentityField(view_name="user-device-detail", lookup_field='uid', read_only=True)
however, it's failing I think because the URL has two fields:
django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "user-device-detail". You may have failed to include the related model in your API, or incorrectly configured the lookup_field attribute on this field.
How do you use a HyperlinkedIdentityField (or any of the Hyperlink*Field) when the URL has two or more URL template items? (lookup fields)?
I'm not sure if you've solved this problem yet, but this may be useful for anyone else who has this issue. There isn't much you can do apart from overriding HyperlinkedIdentityField and creating a custom serializer field yourself. An example of this issue is in the github link below (along with some source code to get around it):
https://github.com/tomchristie/django-rest-framework/issues/1024
The code that is specified there is this:
from rest_framework.relations import HyperlinkedIdentityField
from rest_framework.reverse import reverse
class ParameterisedHyperlinkedIdentityField(HyperlinkedIdentityField):
"""
Represents the instance, or a property on the instance, using hyperlinking.
lookup_fields is a tuple of tuples of the form:
('model_field', 'url_parameter')
"""
lookup_fields = (('pk', 'pk'),)
def __init__(self, *args, **kwargs):
self.lookup_fields = kwargs.pop('lookup_fields', self.lookup_fields)
super(ParameterisedHyperlinkedIdentityField, self).__init__(*args, **kwargs)
def get_url(self, obj, view_name, request, format):
"""
Given an object, return the URL that hyperlinks to the object.
May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
attributes are not configured to correctly match the URL conf.
"""
kwargs = {}
for model_field, url_param in self.lookup_fields:
attr = obj
for field in model_field.split('.'):
attr = getattr(attr,field)
kwargs[url_param] = attr
return reverse(view_name, kwargs=kwargs, request=request, format=format)
This should work, in your case you would call it like this:
url = ParameterisedHyperlinkedIdentityField(view_name="user-device-detail", lookup_fields=(('<model_field_1>', 'user_pk'), ('<model_field_2>', 'uid')), read_only=True)
Where <model_field_1> and <model_field_2> are the model fields, probably 'id' and 'uid' in your case.
Note the above issue was reported 2 years ago, I have no idea if they've included something like that in newer versions of DRF (I haven't found any) but the above code works for me.
Suppose I have this model:
Class animals(models.Model):
name = models.CharField(max_length = 20)
and I make 3 objects of it, say ob1, ob2, ob3 with ob1.name = cat, ob2.name = dog, ob3.name = cow
Now if I have a url like this www.domain.com/cator www.domain.com/dog, How to capture /cat or /dogfrom the url and check against the names of objects of class animal?
I am trying to implement a view function that takes a parameter from the url, eg: object.name, and execute according to that object.
Any help is appreciated.
Use named groups.
It’s possible to use named regular-expression groups to capture URL
bits and pass them as keyword arguments to a view.
urls.py:
from django.conf.urls import patterns, url
urlpatterns = patterns('',
url(r'^(?P<name>\w+)/$', 'my_view'),)
views.py:
def my_view(request, name=None):
# get a model instance
animal = animals.objects.get(name=name)
Hope that helps.
Let's suppose we have a model, Foo, that references another model, User - and there are Flask-Admin's ModelView for both.
On the Foo admin view page
I would like the entries in the User column to be linked to the corresponding User model view.
Do I need to modify one of Flask-Admin's templates to achieve this?
(This is possible in the Django admin interface by simply outputting HTML for a given field and setting allow_tags (ref) True to bypass Django's HTML tag filter)
Some example code based on Joes' answer:
class MyFooView(ModelView):
def _user_formatter(view, context, model, name):
return Markup(
u"<a href='%s'>%s</a>" % (
url_for('user.edit_view', id=model.user.id),
model.user
)
) if model.user else u""
column_formatters = {
'user': _user_formatter
}
Use column_formatters for this: https://flask-admin.readthedocs.org/en/latest/api/mod_model/#flask.ext.admin.model.BaseModelView.column_formatters
Idea is pretty simple: for a field that you want to display as hyperlink, either generate a HTML string and wrap it with Jinja2 Markup class (so it won't be escaped in templates) or use macro helper: https://github.com/mrjoes/flask-admin/blob/master/flask_admin/model/template.py
Macro helper allows you to use custom Jinja2 macros in overridden template, which moves presentational logic to templates.
As far as URL is concerned, all you need is to find endpoint name generated (or provided) for the User model and do url_for('userview.edit_view', id=model.id) to generate the link.
extra information for #wodow, notice that model.user is wrong if you use pymongo as the backend, because the model in pymongo is a dict type, you can just use model['name'] to replace it
Adding this code to each of your models that have referenced by other models and flask-admin and jinja will take care of the name you want to display on the screen, just replace that with whatever you prefer:
def __unicode__(self):
return self.name # or self.id or whatever you prefer
for example:
class Role(db.Document, RoleMixin):
name = db.StringField(max_length=80, unique=True)
description = db.StringField(max_length=255)
def __unicode__(self):
return self.name
class MasterUser(db.Document, UserMixin):
email = db.StringField(max_length=255)
password = db.StringField(max_length=255)
active = db.BooleanField(default=True)
confirmed_at = db.DateTimeField()
roles = db.ListField(db.ReferenceField(Role), default=[])