I have articles in MongoDB. I want the URLs for the articles to be readable. If I have an article named "How to Use Flask and MongoDB Seamlessly with Heroku", I want the URL to be something like localhost:5000/blog/how-to-use-flask-and-mongodb-seamlessly-with-heroku.
What is the best way to accomplish this? Any pointers in the right direction are appreciated. I wasn't sure exactly where to start on this one.
You are looking for a way to generate a "slug" and use that to identify the post.
If you want to use just a slug, all post titles will have to have a unique slug (which approximately means a unique title). This also means that if you change the post's title, the url could change, which would invalidate bookmarks and other outside links.
A better method is to do something like what Stack Overflow does for questions. If you look at this question's URL, you'll notice it has a unique id and a slug. In fact, the slug is optional, you can still get to this page by removing it from the url.
You'll need a way to generate slugs, and a custom url converter. The inflection library provides a nice way to slugify strings with the parameterize method. The following url converter takes an object and returns a url with the_object.id and the_object.title as a slug. When parsing a url, it will just return the object's id, since the slug is optional.
from inflection import parameterize
from werkzeug.routing import BaseConverter
class IDSlugConverter(BaseConverter):
"""Matches an int id and optional slug, separated by "/".
:param attr: name of field to slugify, or None for default of str(instance)
:param length: max length of slug when building url
"""
regex = r'-?\d+(?:/[\w\-]*)?'
def __init__(self, map, attr='title', length=80):
self.attr = attr
self.length = int(length)
super(IDSlugConverter, self).__init__(map)
def to_python(self, value):
id, slug = (value.split('/') + [None])[:2]
return int(id)
def to_url(self, value):
raw = str(value) if self.attr is None else getattr(value, self.attr, '')
slug = parameterize(raw)[:self.length].rstrip('-')
return '{}/{}'.format(value.id, slug).rstrip('/')
Register the converter so it can be used in routes:
app.url_map.converters['id_slug'] = IDSlugConverter
Use it in a route:
#app.route('/blog/<id_slug:id>')
def blog_post(id):
# get post by id, do stuff
Generate a url for a post. Note that you pass the object ('post'), not just the id, to the id parameter.:
url_for('blog_post', id=post)
# /blog/1234/the-post-title
Converter written by me for the Stack Overflow Python chat room site.
Related
If I have one variable being fetched from url pattern according to this regular expression:
re_path(r'^reset/(?P<uid>[\w]+)/?$', accounts.api.views.SetNewPassword.as_view()),
how to rewrite it to fetch few possible variables: either uid or u32id?
In url, there may be either u32id for links in old password recovery emails or plain uid in new recovery emails. But both must be supported in the view. So we will check kwargs for both of them anyway.
Make several trivial patterns mapped to the same view ordered from more specific to less specific:
path('reset/<uuid:uid>/', accounts.api.views.SetNewPassword.as_view()),
path('reset/<int:id>/', accounts.api.views.SetNewPassword.as_view()),
...
You can make your own path converter to combine your uuids
from django.urls.converters import StringConverter, UUIDConverter
class MyCustomPathConverter(UUIDConverter):
regex = 'regex_of_uuid|regex_of_u32id' # '|' is mandatory to distinguish multiple regex
def to_python(self, value):
return value
def to_url(self, value):
return value
urls.py
from django.urls import path, register_converter
register_converter(MyCustomPathConverter, 'my_uuid_converter')
path('reset/<my_uuid_converter:uid>/', accounts.api.views.SetNewPassword.as_view()),
I've been struggling with this for hours and tried everything!
So basically
I have a class called City:
class City(ndb.Model):
_parent = None
city = ndb.StringProperty(required=True)
created = ndb.DateTimeProperty(auto_now_add=True)
When i make a new city, it is given an automatic ID, such as 6438740092256256
Later on i want to add a Restaurant entity to this city as a child. So I have passed the city ID in the url, like this:
http://www.edkjfsk.com/addrestaurant/6438740092256256
which leads to this bit of code
class PostRestaurant(webapp2.RequestHandler):
def post(self, resource):
key = ????
where resource is 6438740092256256
What i want to do is to be able to retrieve the key using the 6438740092256256 from the url. However every single approach i have tried results in an unknown error.
Ive tried everything, including key = :
City.get_by_id(int(resource))
ndb.Key(City, resource).get()
ndb.Key('City', int(resource))
etc.
You are getting urlsafe string here.
this_key = ndb.Key(urlsafe=resource)
query_result_as_entity = this_key.get()
After converting urlsafe string to key, you can just do get() on it.
I'm using tastypie and I want to create a Resource for a "singleton" non-model object.
For the purposes of this question, let's assume what I want the URL to represent is some system settings that exist in an ini file.
What this means is that...:
The fields I return for this URL will be custom created for this Resource - there is no model that contains this information.
I want a single URL that will return the data, e.g. a GET request on /api/v1/settings.
The returned data should return in a format that is similar to a details URL - i.e., it should not have meta and objects parts. It should just contain the fields from the settings.
It should not be possible to GET a list of such object nor is it possible to perform POST, DELETE or PUT (this part I know how to do, but I'm adding this here for completeness).
Optional: it should play well with tastypie-swagger for API exploration purposes.
I got this to work, but I think my method is kind of ass-backwards, so I want to know what is the common wisdom here. What I tried so far is to override dehydrate and do all the work there. This requires me to override obj_get but leave it empty (which is kind of ugly) and also to remove the need for id in the details url by overriding override_urls.
Is there a better way of doing this?
You should be able to achieve this with the following. Note I haven't actually tested this, so some tweaking may be required. A more rich example can be found in the Tastypie Docs
class SettingsResource(Resource):
value = fields.CharField(attribute='value', help_text='setting value')
class Meta:
resource_name = 'setting'
fields = ['value']
allowed_methods = ['get']
def detail_uri_kwargs(self, bundle_or_obj):
kwargs = {}
return kwargs
def get_object_list(self, request):
return [self.obj_get()]
def obj_get_list(self, request=None, **kwargs):
return [self.obj_get()]
def obj_get(self, request=None, key=None, **kwargs):
setting = SettingObject()
setting.value = 'whatever value'
return setting
The SettingObject must support the getattr and setattr methods. You can use this as a template:
class SettingObject(object):
def __init__(self, initial=None):
self.__dict__['_data'] = {}
if initial:
self.update(initial)
def __getattr__(self, name):
return self._data.get(name, None)
def __setattr__(self, name, value):
self.__dict__['_data'][name] = value
def update(self, other):
for k in other:
self.__setattr__(k, other[k])
def to_dict(self):
return self._data
This sounds like something completely outside of TastyPie's wheelhouse. Why not have a single view somewhere decorated with #require_GET, if you want to control headers, and return an HttpResponse object with the desired payload as application/json?
The fact that your object is a singleton and all other RESTful interactions with it are prohibited suggests that a REST library is the wrong tool for this job.
I only want that someone confirm me that I'm doing things in the right way.
I have this structure: Books that have Chapters (ancestor=Book) that have Pages (ancestor=Chapter)
It is clear for me that, to search for a Chapter by ID, I need the book to search by ancestor query.
My doubt is: do I need all the chain book-chapter to search a page?
For example (I'm in NDB):
class Book(ndb.Model):
# Search by id
#classmethod
def by_id(cls, id):
return Book.get_by_id(long(id))
class Chapter(ndb.Model):
# Search by id
#classmethod
def by_id(cls, id, book):
return Chapter.get_by_id(long(id), parent=book.key)
class Page(ndb.Model):
# Search by id
#classmethod
def by_id(cls, id, chapter):
return Page.get_by_id(long(id), parent=chapter.key)
Actually, when I need to search a Page to display its contents, I'm passing the complete chain in the url like this:
getPage?bookId=5901353784180736&chapterId=5655612935372800&pageId=1132165198169
So, in the controller, I make this:
def get(self):
# Get the id parameters
bookId = self.request.get('bookId')
chapterId = self.request.get('chapterId')
pageId = self.request.get('pageId')
if bookId and chapterId and pageId:
# Must be a digit
if bookId.isdigit() and chapterId.isdigit() and pageId.isdigit():
# Get the book
book = Book.by_id(bookId)
if book:
# Get the chapter
chapter = Chapter.by_id(chapterId, book)
if chapter:
# Get the page
page = Page.by_id(pageId, chapter)
Is this the right way? Must I have always the complete chain in the URL to get the final element of the chain?
If this is right, I suppose that this way of work, using NDB, does not have any impact on the datastore, because repeated calls to this page always hit the NDB cache for the same book, chapter and page (because I'm getting by id, is not a fetch command). Is my suppose correct?
No, there's no need to do that. The point is that keys are paths: you can build them up dynamically and only hit the datastore when you have a complete one. In your case, it's something like this:
page_key = ndb.Key(Book, bookId, Chapter, chapterId, Page, pageId)
page = page_key.get()
See the NDB docs for more examples.
How do you get the model object of a tastypie modelresource from it's uri?
for example:
if you were given the uri as a string in python, how do you get the model object of that string?
Tastypie's Resource class (which is the guy ModelResource is subclassing ) provides a method get_via_uri(uri, request). Be aware that his calls through to apply_authorization_limits(request, object_list) so if you don't receive the desired result make sure to edit your request in such a way that it passes your authorisation.
A bad alternative would be using a regex to extract the id from your url and then use it to filter through the list of all objects. That was my dirty hack until I got get_via_uri working and I do NOT recommend using this. ;)
id_regex = re.compile("/(\d+)/$")
object_id = id_regex.findall(your_url)[0]
your_object = filter(lambda x: x.id == int(object_id),YourResource().get_object_list(request))[0]
You can use get_via_uri, but as #Zakum mentions, that will apply authorization, which you probably don't want. So digging into the source for that method we see that we can resolve the URI like this:
from django.core.urlresolvers import resolve, get_script_prefix
def get_pk_from_uri(uri):
prefix = get_script_prefix()
chomped_uri = uri
if prefix and chomped_uri.startswith(prefix):
chomped_uri = chomped_uri[len(prefix)-1:]
try:
view, args, kwargs = resolve(chomped_uri)
except Resolver404:
raise NotFound("The URL provided '%s' was not a link to a valid resource." % uri)
return kwargs['pk']
If your Django application is located at the root of the webserver (i.e. get_script_prefix() == '/') then you can simplify this down to:
view, args, kwargs = resolve(uri)
pk = kwargs['pk']
Are you looking for the flowchart? It really depends on when you want the object.
Within the dehydration cycle you simple can access it via bundle, e.g.
class MyResource(Resource):
# fields etc.
def dehydrate(self, bundle):
# Include the request IP in the bundle if the object has an attribute value
if bundle.obj.user:
bundle.data['request_ip'] = bundle.request.META.get('REMOTE_ADDR')
return bundle
If you want to manually retrieve an object by an api url, given a pattern you could simply traverse the slug or primary key (or whatever it is) via the default orm scheme?