Decoupling django apps 2 - how to get object information from a slug in the URL - django

I am trying to de-couple two apps:
Locations - app containing details about some location (town, country, place etc)
Directory - app containing details of places of interest (shop, railway station, pub, etc) - all categorised.
Both locations.Location and directory.Item contain lat/lng coords and I can find items within a certain distance of a specific lat/lng coord.
I'd like to use the following URL structure:
/locations/<location_slug>/directory/<category_slug>/
But I don't want to make my directory app reliant on my location app.
How can I translate this url to use a view like this in my directory app?
items_near(lat, lng, distance, category):
A work around would be to create a new view somewhere that translates this - but where should I put that? if it goes in the Directory app, then I've coupled that with my Location app, and vice versa.
Would a good idea be to place this workaround code inside my project URLs file? Thus keeping clear of both apps? Any issues with doing it like this?

For your urlpattern to work, the view function invoked has to be aware of both Locations and Directories. The short answer is you can put this view function anywhere you want - it's just a python function. But there might be a few logical places for it, outside of your Directory or Location app, that make sense.
First off, I would not put that view code in in your top-level urls.py, as that file is intended for URLconf related code.
A few options of where to put your view:
Create a new view function in a file that lives outside of any particular app. <project_root>/views.py is one possibility. There is nothing wrong with this view calling the item_near(..) view from the Directory app.
# in myproject/urls.py
urlpatterns = (
...
(r'/locations/(?P<location_slug>[\w\-]+)/directory/(?P<category_slug>[\w\-]+)/',
'myproject.views.items_near_from_slug')
)
# in myproject/views.py
from directory.views import items_near
def items_near_from_slug(request, location_slug, category_slug):
location = get_object_or_404(Location, slug=location_slug)
distance = 2 # not sure where this comes from
# And then just invoke the view from your Directory app
return items_near(request, location.lat, location.lng, distance, category_slug)
Create a new app and put the code there, in <my_new_app>/views.py. There is no requirement that a Django app need to have a models.py, urls.py, etc. Just make sure to include the __init__.py if you want Django to load the app properly (if, for instance, you want Django to automatically find templatetags or app specific templates).
Personally, I would go with option 1 only if the project is relatively simple, and <project_root>/views.py is not in danger of becoming cluttered with views for everything. I would go with option 2 otherwise, especially if you anticipate having other code that needs to be aware of both Locations and Directories. With option 2, you can collect the relevant urlpatterns in their own app-specific urls.py as well.

From the django docs here if you're using django >=1.1 then you can pass any captured info into the included application. So splitting across a few files:
# in locations.urls.py
urlpatterns = ('',
(r'location/(?P<location_slug>.*?)/', include('directory.urls')),
#other stuff
)
# in directory.urls.py
urlpatterns = ('',
(r'directory/(?P<directory_slug>.*?)/', 'directory.views.someview'),
#other stuff
)
# in directory.views.py
def someview(request, location_slug = None, directory_slug = None):
#do stuff
Hope that helps. If you're in django < 1.1 I have no idea.

Irrespective of how much ever "re-usable" you make your app, inevitably there is a need for site-specific code.
I think it is logical to create a "site-specific" application that uses the views of the reusable and decoupled apps.

Related

What would be considered the root directory to a third party javascript in Django

When using Send Pulse they instruct you place a javascript in the head of your template. This javascript then refers to 2 other scripts that they instruct you to place in the root of you web site. Where can these be placed so that their javascript will automatically find them in the "web root"?
The point is that the service is expecting to find the scripts directly under /, eg mywebsite.com/script1.js. But in Django static files are usually under /static or the equivalent. There is nowhere you can just "place" them to get them to appear at the right URL.
But that doesn't mean you can't do it. The best thing to do would be to add an explicit mapping in your web server (nginx or Apache) for those scripts. For example, in nginx:
location /script1.js { alias /path/to/staticdir/script1.js; }
You have a number of possible options. In no particular order:
#daniel-roseman's suggestion of using a rewrite/alias configured in your webserver
Modify the javascript that you're going to place in the head of your page, so that it loads the scripts from your /static folder (followed by placing those scripts there, of course)
Use an HttpResponse to return these script(s) from a view function, and then map the target URLs (like mywebsite.com/script1.js) to the view.
This might look like:
# urls.py - assuming >= Django 2.0
from django.urls import path
urlpatterns = [
# place script1.js into your static folder, then reference its location within the static folder
path('script1.js', views.javascript_loader, {'script_path': 'script1.js'}),
]
# views.py
from django.http import HttpResponse
from django.conf import settings
import os
def javascript_loader(request, script_path):
# note: you should cache this content for the use case you've described, just
# consider this an illustration of how to load/return content from a static
# file using a view.
full_script_path = os.path.join(settings.STATIC_ROOT, script_path)
with open(full_script_path, 'r') as f:
javascript_contents = f.read()
return HttpResponse(javascript_contents, mimetype="text/javascript")
Again, not sure which solution would be best for your needs, but one of those should get you the result you need.

Django syndication framework: prevent appending SITE_ID to the links

According to the documentation here: https://djangobook.com/syndication-feed-framework/
If link doesn’t return the domain, the syndication framework will
insert the domain of the current site, according to your SITE_ID
setting
However, I'm trying to generate a feed of magnet: links. The framework doesn't recognize this and attempts to append the SITE_ID, such that the links end up like this (on localhost):
<link>http://localhost:8000magnet:?xt=...</link>
Is there a way to bypass this?
Here's a way to do it with monkey patching, much cleaner.
I like to create a separate folder "django_patches" for these kinds of things:
myproject/django_patches/__init__.py
from django.contrib.syndication import views
from django.contrib.syndication.views import add_domain
def add_domain_if_we_should(domain, url, secure=False):
if url.startswith('magnet:'):
return url
else:
return add_domain(domain, url, secure=False)
views.add_domain = add_domain_if_we_should
Next, add it to your INSTALLED_APPS so that you can patch the function.
settings.py
INSTALLED_APPS = [
'django_overrides',
...
]
This is a bit gnarly, but here's a potential solution if you don't want to give up on the Django framework:
The problem is that the method add_domain is buried deep in a huge method within syndication framework, and I don't see a clean way to override it. Since this method is used for both the feed URL and the feed items, a monkey patch of add_domain would need to consider this.
Django source:
https://github.com/django/django/blob/master/django/contrib/syndication/views.py#L178
Steps:
1: Subclass the Feed class you're using and do a copy-paste override of the huge method get_feed
2: Modify the line:
link = add_domain(
current_site.domain,
self._get_dynamic_attr('item_link', item),
request.is_secure(),
)
To something like:
link = self._get_dynamic_attr('item_link', item)
I did end up digging through the syndication source code and finding no easy way to override it and did some hacky monkey patching. (Unfortunately I did it before I saw the answers posted here, all of which I assume will work about as well as this one)
Here's how I did it:
def item_link(self, item):
# adding http:// means the internal get_feed won't modify it
return "http://"+item.magnet_link
def get_feed(self, obj, request):
# hacky way to bypass the domain handling
feed = super().get_feed(obj, request)
for item in feed.items:
# strip that http:// we added above
item['link'] = item['link'][7:]
return feed
For future readers, this was as of Django 2.0.1. Hopefully in a future patch they allow support for protocols like magnet.

Process Views outside viewflow.frontend

I want to integrate processes, starting them etc., outside of the standard viewflow.frontend. To do I have been trying to create a simple page where I can start a new process, but have been struggling to find a way to implement it.
One approach was to defined a url to url('^start_something, CustomStartView.as_view()) with CustomStartView subclassing CreateProcessView from viewflow.flow.views.start. This ended in getting error after error, whenever I fixed one. I am quite sure now that this isn't the right way to do it, also because the View is used as a parameter of the Flow class itself and probably needs to be used differently than a common view.
What is the right way to do it?
The Viewflow views need to be parametrized by flow_class and flow_task instance. So you can include a start view as following::
url('^start/', CreateProcessView.as_view(), {
'flow_class': MyFlow,
'flow_task': MyFlow.start})
To add a task view URL config, you need to use process_pk and task_pk parameters
url('^(?P<process_pk>\d+)/approve/(?P<task_pk>\d+)/', ApproveView.as_view(), {
'flow_class': MyFlow,
'flow_task': MyFlow.approve
})
For each node, you also can enable detail, and various actions URLs, ex:
url('^(?P<process_pk>\d+)/approve/(?P<task_pk>\d+)/detail/', DetailTaskView.as_view(), {
'flow_class': MyFlow,
'flow_task': MyFlow.approve
}),
url('^(?P<process_pk>\d+)/approve/(?P<task_pk>\d+)/cancel/', CancelTaskView.as_view(), {
'flow_class': MyFlow,
'flow_task': MyFlow.approve
}),
All of that's a big cumbersome.
Recommended way
You can just include Flow.instance.urls that contains all URLs collected and ready for inclusion into an URL config.
url('^myflow', include(MyFlow.instance.urls, namespace='myflow'))
And at the last, to enable task list views, you can put URL entries manually, ex
url('^myflow/inbox/', TaskListView.as_view(), {
'flow_class': MyFlow}, name="tasks")
or just use as viewflow.flow.viewset.FlowViewSet class
myflow_urls = FlowViewSet(MyFlow).urls
urlpatterns = [
url(r'^myflow/', include(myflow_urls, namespace='myflow'))
]
This is the recommended way to include viewflow URLs into a django URL Config. To customize views used in that URL config, you can subclass the FlowViewSet class and provide views in the nodes definition, ex
class MyFlow(Flow):
start = flow.Start(detail_view_class=MyDetailTaskView)
For the sample usage, you can checkout the Viewflow Custom UI cookbook example - https://github.com/viewflow/cookbook/tree/master/custom_ui

django settings variables get lost in while being passed to templates

i have a weird problem.
Basically, in my settings.py file i have 4 variables
URL_MAIN = 'http://www.mysite'
URL_JOBS = 'http://jobs.mysite'
URL_CARS = 'http://cars.mysite'
URL_HOMES = 'http://homes.mysite'
In my views.py i have the usual:
from settings import *
I have 6 views calling them and just returning them to templates inside the context:
class CarsHp(TemplateView):
...
class JobsHp(TemplateView):
...
class HomesHp(TemplateView):
...
class CarsList(TemplateView):
...
class JobsList(TemplateView):
...
class HomesList(TemplateView):
...
which are being called in urls by
CarsList.as_view()
...
All of those views have the same statement:
context['URL_MAIN'] = URL_MAIN
...
for all 4 variables.
In templates i'm correctly getting all 4 of them, except for URL_MAIN, which "gets lost" in 2 of those 6 views. I'm accessing them with classical {{ URL_MAIN }} and i've been trying everything, from moving to renaming, but still that URL_MAIN doesn't show up (i get empty string, no errors of sort) after being served from 2 of those views. All the functions basically share the same code (except for the querying and data processing part) and those settings' variables are just being assigned and returned off. Not any sort of check nor modification. I've been trying with django's shell, and i could always retrieve them.
We're being served by apache, with some proxypassing configurations for the robots.txt file and static files. Nothing "serious".
I'm not posting all the 6 views source codes just because they're long and the relevant parts are all described above. But i can post them if you want,i just don't know if it is actually useful since i've been triple checking all the sources for clashing on names or double declarations or incorrect use.
Thanks all in advance, this is really stunning my brain
Ideally, you should use template context processors for this. It will cut down your code and allow you to see exactly where the problem is.
Make a file in your projects called urls_context_processor.py (or similar) and put your variables in there:
def common_urls(request):
return {
'URL_MAIN': "http://...",
'URL_JOBS': "http://...",
'URL_CARS': "http://...",
'URL_HOME': "http://...",
}
and in your settings.py
TEMPLATE_CONTEXT_PROCESSORS = = (
....
'my_project.urls_context_processor.common_urls',)
now the urls variables will be automatically available in all your template, and you won't need to hard code them into every view.

Django: Passing data to view from url dispatcher without including the data in the url?

I've got my mind set on dynamically creating URLs in Django, based on names stored in database objects. All of these pages should be handled by the same view, but I would like the database object to be passed to the view as a parameter when it is called. Is that possible?
Here is the code I currently have:
places = models.Place.objects.all()
for place in places:
name = place.name.lower()
urlpatterns += patterns('',
url(r'^'+name +'/$', 'misc.views.home', name='places.'+name)
)
Is it possible to pass extra information to the view, without adding more parameters to the URL? Since the URLs are for the root directory, and I still need 404 pages to show on other values, I can't just use a string parameter. Is the solution to give up on trying to add the URLs to root, or is there another solution?
I suppose I could do a lookup on the name itself, since all URLs have to be unique anyway. Is that the only other option?
I think you can pass a dictionary to the view with additional attributes, like this:
url(r'^'+name +'/$', 'misc.views.home', {'place' : place}, name='places.'+name)
And you can change the view to expect this parameter.
That's generally a bad idea since it will query the database for every request, not only requests relevant to that model. A better idea is to come up with the general url composition and use the same view for all of them. You can then retrieve the relevant place inside the view, which will only hit the database when you reach that specific view.
For example:
urlpatterns += patterns('',
url(r'^places/(?P<name>\w+)/$', 'misc.views.home', name='places.view_place')
)
# views.py
def home(request, name):
place = models.Place.objects.get(name__iexact=name)
# Do more stuff here
I realize this is not what you truly asked for, but should provide you with much less headaches.