📜 ⬆️ ⬇️

Mobile version for Django project



Every day, smartphone users occupy an increasing share of the Internet. According to LiveInternet, the share of Russian Android OS users has already exceeded the share of Windows7. On weekends, users of mobile platforms use the Internet much more often. The same trend is observed in the world. All this once again proves the need to adapt the site for smartphones and tablets.

How you can adapt your Django-project for mobile devices, I will discuss in this article. But first, let's look at what are the options for creating a mobile version of the site.

1. Options for the mobile version of the site

1.1 Adaptive layout


In this case, we give the same amount of data for a large and mobile version of the site. This approach is the easiest for backend development, everything is decided by layout.
')
Of the benefits:

Of the minuses:

This approach is well suited for small sites. When there is a lot of output to the page, ease of implementation creates a big problem in usability.

1.2 Mobile version on subdomain


In fact, these are two separate sites. This approach solves the problem of excess traffic, gives more flexibility and opportunities in developing a version for mobile devices. However, the question of which version to show the user is decided by the server, and not by the browser. You also need to enable the user to choose which version of the site he needs, and “make friends” with both versions of the site with redirects and alternates.

Different URLs of one page lead to a lack of this method: relative links in the site materials can lead to pages that are not in the mobile version. Therefore, you have to specify absolute links to the main domain and then redirect the user to the desired version. The solution to this drawback will be fully appropriate large and mobile versions, but in this case it is more correct to go in the third way.

1.3 Mobile version on the same domain


This is a refinement of the first approach and the solution to its minus with traffic and excess load. It is implemented in the same way as with a subdomain: you determine which version the client needs, and give the necessary amount of data to the desired template. The same URL for both versions of the site is definitely a plus. Although the problem of organizing content for both versions still remains, but it is already easier to solve, since there are no restrictions on the same data.

1.4 Our experience


In the Mail.Ru Group content project department, we use the second approach, although we are moving smoothly towards the third. Kids Mail.Ru and Health Mail.Ru projects are written in Django, both have mobile and / or touch versions. Despite the fact that the projects under the hood are slightly different, the mechanism for creating mobile versions is the same. This is what I want to share with you.



2. Original agreements

2.1 All routes we call


url(r'^$', views.MainIndexView.as_view(), name='health-main-index') url(r'^(?P<slug>[-\w]+)/$', views.NewsDetailView.as_view(), name='health-news-detail') 

And we always appeal to them by this name.

 reverse('health-main-index') 

We never collect URLs ourselves in controllers or templates, they are listed only in urls.py. DRY .

2.2 Using django-hosts


About this library already mentioned on Habré. Initially, we used it on the “children” for a forum on a subdomain, now the forum has moved from us to the main host, and this library is used only for mobile versions.

In short, how it works: you connect middleware, which, depending on the Host header, replaces the URL scheme. In addition to the jung reverse function, you can use reverse_full from this library, which builds an absolute URL. A similar host_url tag can be used in templates. The functions used reverse_host , get_host also taken from this application.

2.3 Separate controllers for large and mobile versions


Despite the fact that sometimes the controllers of the large and mobile versions of the site put the same data into context, we divided them into separate functions / classes. Yes, there is duplication between them, but the code becomes clearer without unnecessary abstractions and checks, when what functions are needed and in what form.

3. Mobile version development

Mobile version should solve the following tasks:

  1. Determination of the site version and redirect for the corresponding devices.
  2. The ability of the user to abandon the mobile version and use the main one.
  3. Redirects should be redirected to the appropriate mobile version and vice versa. It is not good to throw the user home and force him to search from the beginning.
  4. If the user got to the missing page in the mobile version, for which there is an analogue in the large version, he needs to explicitly report this. This task was not, if both versions fit each other.
  5. You must specify alternate and canonical meta tags if the mobile analogue is available for the page.

3.1 Determining the version of the site


From which device the user came to our project, we define it using our nginx module. It looks like this:

 set $mobile $rb_mobile; if ($cookie_mobile ~ 0) { set $mobile ""; } # discard by cookie proxy_set_header X-Mobile-Version $mobile; 

The module determines the type of version that needs to be shown (m or touch), but if the user has a mobile cookie, we ignore it. The result is transmitted in the form of an http header to the backend.

Further processing of the request takes place in middleware.

 class MobileMiddleware(object): def process_request(self, request): if request.method != 'GET': # redirect only GET requests return mobile_version = request.META.get(MOBILE_HEADER, '') if not mobile_version: # redirect only for mobile devices return hostname = request.host.name if hostname in settings.MOBILE_HOSTS: # redirect only for main version return if mobile_version == 'm': host = get_host('mobile-' + hostname) elif mobile_version == 'touch': host = get_host('touch-' + hostname) else: # wrong header value return if not is_valid_path(request.path, host.urlconf): # url doesn't exist in mobile version return redirect_to = u'http://{}{}'.format(reverse_host(host), request.get_full_path()) return http.HttpResponseRedirect(redirect_to) 

User redirection is possible if:

In general, which version to give to the user is determined by the UserAgent in the middleware. There you need to check the value of mobile cookies I myself did not use the django-mobile application, perhaps there are other more accurate libraries for determining the type of device. Suggest them in the comments.

3.2 Transition to a larger version of the site


We sent the user to the mobile version, we will also give him the opportunity to switch back to the larger version. The basements of our projects contain a link of the form /go-health/ , through which the transition is made.

 url(r'^go-health(?P<path>/.*)$', 'health.mobile.views.go') 

Unfortunately, sometimes the pages of the mobile version differ from the main one. Information that fits easily on a large version is divided into 3 pages in the mobile. Therefore, discarding the subdomain and redirecting to the same URL would be wrong. We chose the following algorithm:

  1. We determine the name of the route of the page on which we are located.
  2. The controller function may contain a special attribute go_view_name . In this case, we redirect to a page with this (different) name of the route. This is necessary just for the case when several pages of one version correspond to one page of a large version.
  3. In other cases, redirect to the big version of the route with the same name.

Thus, the name of the routes acts as a bundle between the controllers of the large and mobile versions.

 @never_use_mobile def go(request, path): meta_query_string = request.META.get('QUERY_STRING', '') query_string = '?' + iri_to_uri(meta_query_string) if meta_query_string else '' main_host = get_main_host(request.host) try: resolver_match = resolve(path) except Resolver404: pass else: if hasattr(resolver_match.func, 'go_view_name'): redirect_to = 'http:%s%s' % (reverse_full( main_host.name, resolver_match.func.go_view_name, view_args=resolver_match.args, view_kwargs=resolver_match.kwargs), query_string) return HttpResponseRedirect(redirect_to) # path matches url patterns, otherwise 404 resolver_match = resolve(path, main_host.urlconf) redirect_to = 'http:%s%s' % (reverse_full( main_host.name, resolver_match.view_name, view_args=resolver_match.args, view_kwargs=resolver_match.kwargs), query_string) return HttpResponseRedirect(redirect_to) 

The go_url_name attribute go_url_name assigned through the decorator

 def go(url_name): def decorator(view_func): @wraps(view_func, assigned=available_attrs(view_func)) def _wrapped_view_func(*func_args, **func_kwargs): return view_func(*func_args, **func_kwargs) _wrapped_view_func.go_url_name = url_name return _wrapped_view_func return decorator @go('health-news-index') def rubric_list(request): ... 

And the decorator never_use_mobile puts a mobile never_use_mobile to cancel automatic redirect

 def never_use_mobile(view_func): @wraps(view_func, assigned=available_attrs(view_func)) def _wrapped_view_func(request, *args, **kwargs): response = view_func(request, *args, **kwargs) set_mobile_cookie(response, 0) return response return _wrapped_view_func 

Unfortunately, the touch versions evolve after the main sections and do not always correspond to pages on a larger version, so this code has to be kept.
The go_view_name attribute simply replaces the name of the route for the analog page. This is a rather limited solution, but it still suffices.

3.3 404 for the mobile version


You can not just inform the user that the page is not found, but also indicate that such a page is in the full version of the site. At the same time, it is not enough to check the URL by the URL scheme: the request /news/foo/ corresponds to the URL scheme, but there is no news. Therefore, we must try to perform the function of the controller in the main URL scheme. There is another subtlety: it is necessary to replace the current URL scheme for a large version, since it is needed by the reverse functions and the url tag. Otherwise, you will render a large version page in the URL scheme of the mobile.

 def page_not_found(request): current_host = request.host hostname = current_host.name main_host = get_host(hostname.replace('mobile-', '')) try: # path matches url patterns resolver_match = resolve(request.path, urlconf=main_host.urlconf) except Resolver404: return mobile_404(request) set_urlconf(main_host.urlconf) try: # function returns not 404 with passed arguments resolver_match.func(request, *resolver_match.args, **resolver_match.kwargs) except Http404: set_urlconf(current_host.urlconf) return mobile_404(request) set_urlconf(current_host.urlconf) meta_query_string = request.META.get('QUERY_STRING', '') query_string = '?' + iri_to_uri(meta_query_string) if meta_query_string else '' redirect_to = 'http:%s%s' % (reverse_full( main_host.name, resolver_match.view_name, view_args=resolver_match.args, view_kwargs=resolver_match.kwargs), query_string) return mobile_fallback404(request, redirect_to) 

3.4 Meta tags alternate and canonical


These URLs are built using the functions or template tags of the django-host application.

 context['canonical'] = build_canonical(reverse_full('www', 'health-news-index')) context['alternate'] = { 'touch': build_canonical(reverse_full('touch-www', 'health-news-index')) } 

4. Instead of conclusion

I would like to repeat that the main implementation difficulties are caused by the discrepancy between the main and mobile versions. While it is impossible to develop a mobile version at the same time as a large one, I have to go this way and keep these checks in the code. Perhaps in the near future we will switch to the mobile version on the same domain and write separately about this method.

Source: https://habr.com/ru/post/239343/


All Articles