Showing posts with label django-templates. Show all posts
Showing posts with label django-templates. Show all posts

Wednesday, September 19, 2018

get request context in Advanced custom template tags

Leave a Comment

In Django simple and inclusion template tags allow getting the request context with

@register.simple_tag(takes_context=True) 

Official documentation for custom template tags - inclusion tags.

However, for custom tags, I don't see how this is done.

What I am trying to do is extend the i18n {% trans %} tag, to look for translation in the database first, before using gettext. I need access to the request.Language from the custom template tag.

1 Answers

Answers 1

From the Django doc of custom template tags, it's also possible to add takes_context for customs tags

import datetime from django import template  register = template.Library()   @register.simple_tag(takes_context=True) def current_time(context, format_string):      #use "context" variable here     return datetime.datetime.now().strftime(format_string)


I'm not sure how to override the existing tag at this particular scenario :(
Anyway, what I'm suggesting is, create a simple_tag that takes the context and do your logic inside the tag and return the translation text from DB. If it's not in DB, return a boolean False. Now in template, check these thing using if tag.

from django import template  register = template.Library()   @register.simple_tag(takes_context=True) def is_db_available(context):     # access your context here and do the DB check and return the translation text or 'False'     if translation_found_in_DB:         return "Some translation text"     return False

and in template

{% load custom_tags %} {% load i18n %} {% is_db_available as check %}  {% if check %}  <!-- The if condition  -->     {{ check }} {% else %}     {% trans "This is the title." %} <!-- Calling default trans tag  --> {% endif %}
Read More

Saturday, August 11, 2018

Django's get_current_language always returns “en”

Leave a Comment

In my Django 2.0 site, I want to set the lang atribute of the html tag to the current locale's language. In my base.html which other templates extend, I use get_current_language in the following way

{% load i18n %}  {% get_current_language as LANGUAGE_CODE %} <!DOCTYPE html> <html lang="{{ LANGUAGE_CODE }}">  ... </html> 

The site has translations for multiple languages. If I switch the language in the browser, I see the correct translations, but the lang attribute will always contain en.

In my settings.py I have

USE_I18N = True LANGUAGE_CODE = 'en-us' 

and based on the suggestion of Goran the following middleware order

MIDDLEWARE = [     'django.contrib.sessions.middleware.SessionMiddleware',     'django.middleware.locale.LocaleMiddleware',     'django.middleware.common.CommonMiddleware',     'django.middleware.csrf.CsrfViewMiddleware',     'django.contrib.auth.middleware.AuthenticationMiddleware',     'django.contrib.messages.middleware.MessageMiddleware', ] 

The LANGUAGES setting is unset.

As suggested by Kostadin Slavov I have tried printing the language from the view. It seems that get_current_language calls django.utils.translation.get_language, so I have inserted the following in my view

from django.utils import translation                                         print(translation.get_language())                                            

It prints the correct value (eg de when accessing the view with a browser set to German).

What else am I missing?

1 Answers

Answers 1

I tried to simulate your environment with these steps:

$ cd ~ $ python3 -m venv ~/venvs/mysite $ source ~/venvs/mysite/bin/activate $ pip install django==2.0.8 $ django-admin startproject mysite 

Then I updated the generate code as in your example:

  • mysite/settings.py

    ... MIDDLEWARE = [     'django.middleware.security.SecurityMiddleware',     'django.contrib.sessions.middleware.SessionMiddleware',     'django.middleware.locale.LocaleMiddleware',     'django.middleware.common.CommonMiddleware',     'django.middleware.csrf.CsrfViewMiddleware',     'django.contrib.auth.middleware.AuthenticationMiddleware',     'django.contrib.messages.middleware.MessageMiddleware',     'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ... TEMPLATES = [     {         'BACKEND': 'django.template.backends.django.DjangoTemplates',         'DIRS': ['templates'],         'APP_DIRS': True,         'OPTIONS': {             'context_processors': [                 'django.template.context_processors.debug',                 'django.template.context_processors.request',                 'django.contrib.auth.context_processors.auth',                 'django.contrib.messages.context_processors.messages',             ],         },     }, ] ... 
  • mysite/urls.py

    from django.contrib import admin from django.urls import path from django.views.generic.base import TemplateView  urlpatterns = [     path('', TemplateView.as_view(template_name='base.html'), name='home'),     path('admin/', admin.site.urls), ] 
  • templates/base.html

    {% load i18n %} {% get_current_language as LANGUAGE_CODE %} <!DOCTYPE html> <html lang="{{ LANGUAGE_CODE }}"> <body> <pre>LANGUAGE_CODE = {{ LANGUAGE_CODE }}</pre> <body> </html> 

With the Django generated code and my few above updates I can see different language code if I switch the language of my browser visiting http://localhost:8000/ after starting it with:

$ python manage.py runserver 

Try my steps on your local environment and check if it works, and then compare your project to the code above.

Update

Try to use diffsettings to see "differences between the current settings file and Django’s default settings".

Read More

Wednesday, June 20, 2018

Use query result from semantic UI inside Django URL Template tag

Leave a Comment

I'm using Django and semantic UI to create a search box. Everything works fine except for the URL parameters.

$('.ui.search').search({          type          : 'standard',         minCharacters : 2,                       apiSettings   : {             onResponse: function(parcelleResponse) {                                         //DO Something                 return response;             },             url: "/myUrl/{query}"         }            }); 

I would like to use the URL template tag system to specify the URL :

{% url 'searchParcelle' {query} %}   

But as the results returned by Semantic UI are stored inside a variable {query}, I've got a template error :

Could not parse the remainder: '{query}' from '{query}' 

Do you know how I could resolve that? I could keep it like this, but as my Prod URL (virtual host) is a bit different, I have to change it every time.

Thanks for your help

2 Answers

Answers 1

There are two problems with your code:

  1. you do not have quotes around {query}, so Django does not treat it like a string
  2. the curly brackets in the URL will be escaped, resulting in a URL like this: /myUrl/%7Bquery%7D/

Additionally, your URL definition might only allow alphanumeric characters and not match with the argument {query}.

The proper way to do this would be to get the URL pattern and transform it into the template URL format, but that would require diving deep into the internals of Django's URL resolving and is probably not worth the hassle.

A more pragmatic approach would be to reverse the URL with a placeholder, which you then replace in JavaScript.

var url = "{% url 'searchParcelle' 'QUERYPLACEHOLDER' %}".replace(     'QUERYPLACEHOLDER', '{query}' ) $('.ui.search').search({      type          : 'standard',     minCharacters : 2,                   apiSettings   : {         onResponse: function(parcelleResponse) {                                     //DO Something             return response;         },         url: url     }        }); 

Answers 2

A more pragmatic approach would be to reverse the URL with a placeholder, which you then replace in JavaScript.

In fact I did exactly what you have suggested and it working as expected now ! Thanks for your help Daniel Hepper.

_urlSearch  = "{% url 'search'  '--MySearchValue--' %}"; _urlDisplay = "{% url 'display' '--MySearchValue--' %}";     $('.ui.search').search({   ...  url: _urlSearch.replace('--MySearchValue--', '{query}') }); 
Read More

Monday, April 16, 2018

django url tag not being called

Leave a Comment

I have a template structure similar to this:

    #X_List.html     <div class="row">         {% include './X_List_Table.html' %}     </div>     <div id="confirm" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="testmodal" aria-hidden="true">     <div class="modal-dialog modal-sm" role="document">         <div class="modal-content"></div>     </div> </div>      #X_List_Table.html     <table>       <thead>         <tr>           <th>Desc</th>           <th>Activate</th>         </tr>       </thead>       <tbody>         {% for item in x_list %}           <tr>             <td>{{ item.id }}</td>             <td><a data-toggle="modal" href="{% url 'x:x_quantity' item.id %}" data-target="#confirm">Click me</a></td>           </tr>         {% endfor %}           </tbody>     </table> 

My view is defined as:

#views.py def x_quantity(request, id):   return render(request, 'modal.html', {'quantity': X.objects.filter(pk=id).count()} 

and the modal.html:

<div class="modal-header">     <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>     <h3>Attention</h3> </div> <div class="modal-body">     <p>The number of elements is {{ quantity }}</p> </div> <div class="modal-footer"></div> 

The problem is:

Supposing that I have 2 elements in the table, their urls would be:

  • 'x/x_quantity/1'
  • 'x/x_quantity/2'

Consider that for these elements:

  • One returns a QuerySet with atleast 1 element
  • One returns an empty QuerySet

When I click on the link, it should run the view, get the quantity based on the id of the element, return it as a context variable to the modal so it can be displayed.

The problem is:

When I click on a link, the view is being called with the id of the element, which can be confirmed by looking at the server shell [06/Apr/2018 17:00:23] "GET /x/x_quantity/1 HTTP/1.1" 200 898.

If I click on the other element, THE VIEW IS NOT BEING CALLED, there is no request going out.

What I intend to do is to display a modal with the quantity of the element clicked.

Is this a confusion on my part regarding how the {% url 'app:app_view' var %} should behave on a href or I'm not supposed to do this and should, instead, use AJAX?

Perhaps this is related with "refreshing" context variables as well?

2 Answers

Answers 1

The explanation for the behavior you are seeing can be found in the Bootstap documentation:

If a remote URL is provided, content will be loaded one time via jQuery's load method and injected into the .modal-content div. If you're using the data-api, you may alternatively use the href attribute to specify the remote source. An example of this is shown below:

If you want to use the same modal to load different content, you have to use Ajax.

A (quite ugly) workaround would be to render a modal for each item in x_list. Just be aware that the value doesn't get updated if you open the same modal twice.

Answers 2

Let me first clarify that before this example I have never used Bootstrap. I found your question interesting so I played a little bit with Bootstrap CDN. Also I do not use a lot of Javascript so everyone feel free to correct any bad practices.

I think what you want is doable using AJAX so here is my solution:

I changed a link to a button because all the modal examples had buttons not links :)

#X_List.html <table>   <thead>     <tr>       <th>Desc</th>       <th>Activate</th>     </tr>   </thead>   <tbody>     {% for x in x_list %}       <tr>         <td>{{ x.id }}</td>         <td><button id="{{ x.id }}" type="button" class="btn btn-info modal-btn">Click me</button></td>       </tr>     {% endfor %}   </tbody> </table>  <!-- Modal --> <div id="myModal" class="modal fade" role="dialog">   <div class="modal-dialog">     <!-- Modal content-->     <div class="modal-content">       <div class="modal-header">         <h4 class="modal-title"></h4>         <button type="button" class="close" data-dismiss="modal">&times;      </button>       </div>       <div class="modal-body"></div>       <div class="modal-footer">         <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>       </div>     </div>   </div> </div> 

Javascript (you will need js-cookie - I used CDN) passes the id to the server side and then shows received quantity at the end in a modal.

$(document).ready(function() {      $(".modal-btn").click(function(event) {         var x_id = $(event.target).attr('id');          $.ajax({             url : "/ajaxquantity/",             type : "POST",             dataType: "json",             data : {                 x_id : x_id,                 csrfmiddlewaretoken: Cookies.get('csrftoken')             },             success : function(json) {                 var quantity = json.quantity;                 $(".modal-title").text('Id: ' + x_id);                 $(".modal-body").text('Quantity: ' + quantity);                 $('#myModal').modal('show');             },             error : function(xhr,errmsg,err) {                 alert(xhr.status + ": " + xhr.responseText);             }         });         return false;     }); }); 

Then you need to add this line into urlpatterns list:

url(r'^ajaxquantity/$', views.ajaxquantity, name='ajaxquantity') 

And finally the server side. I do not know how your model looks like so here I used your query from the question.

# views.py def ajaxquantity(request):     if "x_id" in request.POST:         response_dict = {}         x_id = request.POST.get('x_id')         quantity = X.objects.filter(pk=id).count()         response_dict.update({'quantity': quantity})         return JsonResponse(response_dict)      else:         return render(request, 'yourapp/X_List.html') 

So this worked for me (with a little different QuerySet). It is very important that Jquery is only defined once!!!

Keep in mind that this is a minimal working example.

Read More

Thursday, October 12, 2017

Is it a way to pass variable to 'extended' template in Django?

Leave a Comment

I want to add some flexibility to my layout template but can't find any way to do that.

I'm looking for a way to extend layout template with variable, i.e. to pass variable up in the template tree not down.

# views.py def my_view_func(request):     return render(request, "child.html") 

# child.html {% extends 'layout.html' with show_sidebar=True sidebar_width_class="width_4" %}  <div>Templates stuff here</div> 

# layout.html {% if show_sidebar %}     <div class="{{ sidebar_width_class }}">         {% block sidebar %}{% endblock %}     </div> {% endif %} 

I have to maintain four templates with the difference in few lines of code now. For example, I have two templates that differ from each other by sidebar width class. Am I doing something wrong?

2 Answers

Answers 1

What you need is an include template tag. You can include a template in another template and render that with specific context.

{% include 'layout.html' with sidebar=True sidebar_width=4 %} 

Check docs here: https://docs.djangoproject.com/en/1.9/ref/templates/builtins/#include

Answers 2

I suspect that block is what you are looking for in the first place.

Form your block inside the base template like this:

{% block sidebar_wrapper %}     {% if sidebar %}     <div class="width{{sidebar_width}}">         {% block sidebar %}{% endblock %}     </div>     {% endif %} {% endblock sidebar_wrapper%} 

And on your child template:

{% extends 'layout.html' %} {% with sidebar=True sidebar_width=4 %}     {% block sidebar_wrapper %}         {{ block.super }}     {% endblock sidebar_wrapper%} {% endwith%} 
Read More

Saturday, August 26, 2017

Django: efficient template/string separation and override

Leave a Comment

I have a generic Django view that renders a template. The template is in an app which other projects will use. Importing projects will typically subclass the View the app provides. The View has a default template, which does a job with generic wording.

99% of the time, subclassing Views will want to only change the text, so rather than make them duplicate the template for the sake of altering non-markup wording, i'm looking for a way to allow users of the class to replace wording in the template in the most efficient way.

Options explored so far:

  • template partials containing only the text which using apps can override (magic, a lot of user work)
  • A template_strings method on the view which provides a dict of strings which end up in the template context which subclasses can override
  • Using (abusing?) the translation system such that the app provides default english translations and using code can provide their own translations instead (not actually worked this one out yet, just an idea)
  • Doing the above template_strings through AppConfig, but this seems ... yucky like it may get very unweildy with a lot of English strings. If doing this I would create a context-like setup so you don't have to re-declare all strings

Seems like it should be a solved problem to subclass a view which does a complete job and just provide alternate strings for text. Is there a better method than the above? Convention? Something I am missing?

(django 1.11 Python 3.6.2)

3 Answers

Answers 1

You can either inherit TemplateView or add ContextMixin to your view, and then override the get_context_data function like this:

from django.views.generic import TemplateView  class BaseView(TemplateView):     template_name = "common.html"  class SubView(BaseView):     def get_context_data(self, **kwargs):         context = super(SubView, self).get_context_data(**kwargs)         context['content'] = "Some sub view text"         return context 

Update: Use template overriding

If you want to separate the text out, this is the better way to go To allow easily and DRY override template across apps, you might need to install this package (Some other detail here)

We define it similarly as above, but change the template_name instead:

from django.views.generic import TemplateView      class BaseView(TemplateView):         template_name = "main.html"      # on another app     class SubView(BaseView):         template_name = "sub_view.html" 

Then the magic is you can extends and override block of the BaseView template like this:

base_app/templates/main.html

<p>I'm Common Text</p> {% block main %}   <p>I'm Base View</p> {% endblock %} 

sub_app/templates/sub_view.html

{% extends "base_app:main.html" %} {% block main %}   <p>I'm Sub View</p> {% endblock %} 

The result would be:

<p>I'm Common Text</p> <p>I'm Sub View</p> 

Answers 2

Assuming your view is a subclass of TemplateView, you could define the strings in your views get_context_data method. That would let developers that are subclasses your view overwrite the strings in get_context_data. This is similar to you're template_strings option but more idiomatic since it's leveraging the get_context_data method from Django.

Answers 3

Afaik you covered the options pretty well. My example is probably just a variant of the the template strings but maybe it helps anyway...

class DefaultStringProvider():     TITLE = 'Hello'     DESCRIPTION = 'Original description'     CATEGORY = 'Stuff'  class MyTemplateBaseView(TemplateView):     def get_context_data(self, **kwargs):         return super(MyTemplateBaseView, self).get_context_data(             provider=self.get_string_provider(), **kwargs)      def get_string_provider(self):         return DefaultStringProvider()  class OtherView(MyTemplateBaseView):     template_name = 'welcome.html'      def get_string_provider(self):         p = DefaultStringProvider()         p.TITLE = 'Hello'         p.DESCRIPTION = 'New description'         return p 

The idea is to have a default string provider and the base view populates the context with it through get_string_provider().

It will at least be quite clear which strings can be overridden for a user extending the base class and it will not interfere with translations.

Read More

Monday, October 17, 2016

How to read the json file of a dinamical way in relation to their size structure

Leave a Comment

I have the following JSON file named ProcessedMetrics.json which is necessary read for send their values to some template:

     {   "paciente": {     "id": 1234,     "nombre": "Pablo Andrés Agudelo Marenco",     "sesion": {       "id": 12345,       "juego": [         {           "nombre": "bonzo",           "nivel": [             {               "id": 1234,               "nombre": "caida libre",               "segmento": [                 {                   "id": 12345,                   "nombre": "Hombro",                   "movimiento": [                     {                       "id": 1234,                       "nombre": "flexion",                       "metricas": [                         {                           "min": 12,                           "max": 34,                           "media": 23,                           "moda": 20                         }                       ]                     }                   ]                 }               ],               "___léeme___": "El array 'iteraciones' contiene las vitorias o derrotas con el tiempo en segundos de cada iteración",               "iteraciones": [                 {                   "victoria": true,                   "tiempo": 120                 },                 {                   "victoria": false,                   "tiempo": 232                 }               ]             }           ]         }       ]     }   } } 

Through of the following class based view I am reading a JSON file.

class RehabilitationSessionDetail(LoginRequiredMixin,DetailView):     model = RehabilitationSession     template_name = 'rehabilitationsession_detail.html'      def get_context_data(self, **kwargs):         context=super(RehabilitationSessionDetail, self).get_context_data(**kwargs)         is_auth=False          user = self.request.user         if user.is_authenticated():             is_auth=True              with open('ProcessedMetrics.json') as data_file:                 session_data=json.loads(data_file.read())              #Sending a data to template                        context.update({'is_auth':is_auth,                                        'session_data':session_data                                      })        return context 

In my template rehabilitationsession_detail.html I put my tag of this way:

<td>{{session_data.paciente.sesion.juego}}</td>  

Then I get the document json in my template

enter image description here

In my template, I want get the dictionary(before json document) values of a separate way such as follow:

enter image description here

The idea is that without matter the nested levels of the json document I can get the values. Sometimes, the json document will have more identation levels in their structure and other times will be a json document more simple

I would that independently of the json document size (if this have more than one item in your arrays) will be possible read and get all the values.

I try accessing to the specific item from the RehabilitationSessionDetail view of this way:

segment = data["paciente"]["sesion"]["juego"][0]["nivel"][0]["segmento"][0]["nombre"] 

And this works, but not always I will get the same json document structure.

In summary, How to can I get the values (nested and parents) of my json document for send them to the template?

I hope can be clear in my question. Any orientation is highly graceful

1 Answers

Answers 1

If I understand correctly from your question, with different JSON sizes you mean different array sizes? You can loop in the django template to get all the information out. So you would do for a nested visualisation:

{% for nest1 in data["patiente"]["sesion"]["juego"] %} <li>{{ nest1["nombre"] }}</li> {% for nest2 in nest2["nivel"] %} etc... {% endfor %} {% endfor %} 

To get it as a table you would seperate the data and make a loop for each table column

<tr>     {% for nest1 in data["patiente"]["sesion"]["juego"] %}     <td>{{ nest1["nombre"] }}</td>     {% endfor %} </tr>  <tr>     {% for nest1 in data["patiente"]["sesion"]["juego"] %}     {% for nest2 in nest2["nivel"] %}     <td>{{ nest2["relevant_key"] }}</td>     {% endfor %}     {% endfor %} </tr> etc... 

because of the nested json data representation you template code will have to follow this nesting.

Hope I understood your question correctly and hope this helps.

Read More

Tuesday, October 4, 2016

what could cause html and script to behave different across iterations of a for loop?

Leave a Comment

I'm trying to build a side navigation bar where categories are listed and upon clicking a category a respective sub list of subcategories is shown right below the category. And if the category is clicked again, the sub list contracts.

So I'm running a loop across category objects. Inside this outer loop, I'm including a an inner loop to list subcategories and a script that hides the submenu and slidetoggles it only when a category is clicked. I'm using django template tags to dynamically assign class names for my html elements and also to refer to them in the script. So after all for loop iterations, there is a list of subcategory and a dedicated script for each category and they have unique class names so no chance of an overlap. So the weird part is, this works perfectly for most categories, but some of the categories and their submenu remain open and when clicked on the category the page reloads.

I don't get it, what could cause the exact same code (run in a for loop) to behave so differently?

This is my code:

{% load staticfiles %} {% load i18n pybb_tags forumindexlistbycat %} {% catindexlist as catindexlisted %}  {% block body %}<div class="col-sm-12 col-md-12 col-xs-12 col-lg-12 body-container leftsidenavigator" style="margin-top:15px;">     <div class="col-sm-12 col-md-12 col-xs-12 col-lg-12 leftsidenavigator-inner" style="padding:0px;">          <h2><center>Categories</center></h2>              <ul class="catindexlist catlistcat nav-collapse89">                    {% for category in catindexlisted %}                          <li class="catindexlistitem category-name{{category.name}}{{category.name}}" style="font-weight:600;padding-right:20px;"><a href="">{{category.name}}</a></li>                          <ul style="padding:0px;" class="nav-collapse88">                          {% for forum in category|forumindexlistbycat %}                                <li class="catlistforum{{category.name}}{{category.name}} forum-name" style="padding-right:10px;"><a href="{{ forum.get_absolute_url }}">{{forum.name}}</a></li>                          {% endfor %}</ul><script>                           $(function() {                               $(".catlistforum{{category.name}}{{category.name}}").hide();                                     $(".category-name{{category.name}}{{category.name}} a").click(function(e) {                                      e.preventDefault();                                      $(".catlistforum{{category.name}}{{category.name}}").slideToggle();                                           if(!($(this).parent('li').siblings('div').children('ul').children('div').is(":visible"))){                                               $(this).parent('li').siblings('div').children('ul').children('div').is(":visible").slideToggle();                                           }});                                       })                                 </script>                            {% endfor %}                            </ul>                       </div>                       </div>                   {% endblock %}      {% block theme_script %}<script src="{% static "pinax/js/theme.js" %}"></script>{% endblock %} 

4 Answers

Answers 1

Try writing the javascript function after the outer for loop, sometimes this might overlap and redirect. And also try providing spaces between the names of category inside "li".

Answers 2

My advice would be to clean up your JS by making a single function to handle all the clicks. You are already using class on clicks, so why not have one function handle all the clicks?

<script>   $(function() {     // Hide all elements with a class starting with catlistforum     $('[class^="catlistforum"]').hide();      // Assign clicks to all links whose parent has a class starting with catlistforum     // (Though why are you hiding the parents? How will they click the links?)     $('[class^="catlistforum"] a').on("click", function(e) {       e.preventDefault();        // Delegate slideToggle to each click target       $(e.target).slideToggle();        // This seems redundant, but hopefully it will behave the way you want it to behave       if(!($(e.target).parent('li').siblings('div').children('ul').children('div').is(":visible"))) {         $(e.target).parent('li').siblings('div').children('ul').children('div').is(":visible").slideToggle();       }     });   }) </script> 

Of course, if I were you, I would just define two new classes (e.g. catlistforum-hide and catlistforum-toggle) which I would apply to all the elements I wanted to hide and toggle, respectively.

Answers 3

Is there any chance that the category name contains spaces?

Just a tip: You are not using good practice in your code. IMO you should get your javascript code outside of the forloop and remove {{ category_name }} classes. catindexlistitem on click should toggle hidden class (i noticed you use bootstrap) to it's child ul.

By adding a more generic event listener you simplify your code and by using css you improve performance. In case you want to add effects you still can with css3.

Answers 4

The most probable cause is the use of {{category.name}} for class names.

The code snippet doesn't show what values is accepted for category.name and my guess is it can be user input? See naming rules in section Attribute Values what is valid for class names.

It can be solved using template tag slugify ({{category.name|slugify}}) but my recommendation is to try re-design the solution a bit.

Read More

Sunday, June 19, 2016

Display objects from different models at the same page according to their published date

Leave a Comment

I have three different models for my app. All are working as I expected.

class Tender(models.Model):     title = models.CharField(max_length=256)     description = models.TextField()     department = models.CharField(max_length=50)     address = models.CharField(max_length=50)     nature_of_work = models.CharField(choices=WORK_NATURE, max_length=1)     period_of_completion = models.DateField()     pubdat = models.DateTimeField(default=timezone.now)      class Job(models.Model):     user = models.ForeignKey(settings.AUTH_USER_MODEL)     title = models.CharField(max_length=256)     qualification = models.CharField(max_length=256)     interview_type = models.CharField(max_length=2, choices=INTERVIEW_TYPE)     type_of_job = models.CharField(max_length=1, choices=JOB_TYPE)     number_of_vacancies = models.IntegerField()     employer = models.CharField(max_length=50)     salary = models.IntegerField()         pubdat = models.DateTimeField(default=timezone.now)  class News(models.Model):     user = models.ForeignKey(settings.AUTH_USER_MODEL)     title = models.CharField(max_length=150)     body = models.TextField()     pubdat = models.DateTimeField(default=timezone.now) 

Now I am displaying each of them at separate page for each of the model (e.g. in the jobs page, I am displaying only the jobs.). But now at the home page, I want to display these according to their published date at the same page. How can I display different objects from different models at the same page? Do I make a separate model e.g. class Post and then use signal to create a new post whenever a new object is created from Tender, or Job, or News? I really hope there is a better way to achieve this. Or do I use multi-table inheritance? Please help me. Thank you.

Update:

I don't want to show each of the model objects separately at the same page. But like feeds of facebook or any other social media. Suppose in fb, any post (be it an image, status, share) are all displayed together within the home page. Likewise in my case, suppose a new Job object was created, and after that a new News object is created. Then, I want to show the News object first, and then the Job object, and so on.

6 Answers

Answers 1

The following should do want you need. But to improve performance you can create an extra type field in each of your models so the annotation can be avoided.

Your view will look something like:

from django.db.models import CharField  def home(request):     # annotate a type for each model to be used in the template     tenders = Tender.object.all().annotate(type=Value('tender', CharField()))     jobs = Job.object.all().annotate(type=Value('job', CharField()))     news = News.object.all().annotate(type=Value('news', CharField()))      all_items = list(tenders) + list(jobs) + list(news)      # all items sorted by publication date. Most recent first     all_items_feed = sorted(all_items, key=lambda obj: obj.pubdat)      return render(request, 'home.html', {'all_items_feed': all_items_feed}) 

In your template, items come in the order they were sorted (by recency), and you can apply the appropriate html and styling for each item by distinguishing with the item type:

# home.html {% for item in all_items_feed %}     {% if item.type == 'tender' %}     {% comment "html block for tender items" %}{% endcomment %}      {% elif item.type == 'news' %}     {% comment "html block for news items" %}{% endcomment %}      {% else %}     {% comment "html block for job items" %}{% endcomment %}      {% endif %} {% endfor %} 

You may avoid the annotation altogether by using the __class__ attribute of the model objects to distinguish and put them in the appropriate html block.

For a Tender object, item.__class__ will be app_name.models.Tender where app_name is the name of the Django application containing the model.

So without using annotations in your home view, your template will look:

{% for item in all_items_feed %}     {% if item.__class__ == 'app_name.models.Tender' %}      {% elif item.__class__ == 'app_name.models.News' %}      ...     {% endif %} {% endfor %} 

With this, you save extra overhead on the annotations or having to modify your models.

Answers 2

A working solution

There are two working solutions two other answers. Both those involve three queries. And you are querying the entire table with .all(). The results of these queries combined together into a single list. If each of your tables has about 10k records, this is going to put enormous strain on both your wsgi server and your database. Even if each table has only 100 records each, you are needlessly looping 300 times in your view. In short slow response.

An efficient working solution.

Multi table inheritance is definitely the right way to go if you want a solution that is efficient. Your models might look like this:

class Post(models.Model):     title = models.CharField(max_length=256)     description = models.TextField()     pubdat = models.DateTimeField(default=timezone.now, db_index = True)      class Tender(Post):     department = models.CharField(max_length=50)     address = models.CharField(max_length=50)     nature_of_work = models.CharField(choices=WORK_NATURE, max_length=1)     period_of_completion = models.DateField()  class Job(Post):     user = models.ForeignKey(settings.AUTH_USER_MODEL)     qualification = models.CharField(max_length=256)     interview_type = models.CharField(max_length=2, choices=INTERVIEW_TYPE)     type_of_job = models.CharField(max_length=1, choices=JOB_TYPE)     number_of_vacancies = models.IntegerField()     employer = models.CharField(max_length=50)     salary = models.IntegerField()       class News(models.Model):     user = models.ForeignKey(settings.AUTH_USER_MODEL)      def _get_body(self):         return self.description      body = property(_get_body) 

now your query is simply

 Post.objects.select_related(    'job','tender','news').all().order_by('-pubdat')   # you really should slice 

The pubdat field is now indexed (refer the new Post model I posted). That makes the query really fast. There is no iteration through all the records in python.

How do you find out which is which in the template? With something like this.

{% if post.tender %}  {% else %}     {% if post.news %}    {% else %} {% else %} 

Further Optimization

There is some room in your design to normalize the database. For example it's likely that the same company may post multiple jobs or tenders. As such a company model might come in usefull.

How about one without multi table inheritance or multiple queries?

That comes with the courtesy of redis sorted sets. Each time you save a Post, Job or News, object you add it to a redis sorted set.

from django.db.models.signals import pre_delete, post_save from django.forms.models import model_to_dict  @receiver(post_save, sender=News) @receiver(post_save, sender=Post) @receiver(post_save, sender=Job)  def add_to_redis(sender, instance, **kwargs):     rdb = redis.Redis()      #instead of adding the instance, you can consider adding the      #rendered HTML, that ought to save you a few more CPU cycles.      rdb.zadd(key, instance.pubdat, model_to_dict(instance)      if (rdb.zcard > 100) : # choose a suitable number          rdb.zremrangebyrank(key, 0, 100) 

Similarly, you need to add a pre_delete to remove them from redis

The clear advantage of this method is that you don't need any database queries at all and your models continue to be really simple + you get catching thrown in the mix. If you are on twitter your timeline is probably generated through a mechanism similar to this.

Answers 3

A straight forward way is to use chain in combination with sorted:

View

# your_app/views.py from django.shortcuts import render from itertools import chain from models import Tender, Job, News  def feed(request):      object_list = sorted(chain(         Tender.objects.all(),         Job.objects.all(),         News.objects.all()     ), key=lambda obj: obj.pubdat)      return render(request, 'feed.html', {'feed': object_list}) 

Please note - the querysets mentioned above using .all() should be understood as placeholder. As with a lot of entries this could be a performance issue. The example code would evaluate the querysets first and then sort them. Up to some hundreds of records it likely will not have a (measurable) impact on performance - but in a situation with millions/billions of entries it is worth looking at.

To take a slice before sorting use something like:

Tender.objects.all()[:20] 

or use a custom Manager for your models to off-load the logic.

class JobManager(models.Manager):     def featured(self):         return self.get_query_set().filter(featured=True) 

Then you can use something like:

Job.objects.featured() 

Template

If you need additional logic depending the object class, create a simple template tag:

#templatetags/ctype_tags.py from django import template register = template.Library()  @register.filter def ctype(value):     return value.__class__.__name__.lower() 

and

#templates/feed.html {% load ctype_tags %}  <div>     {% for item in feed reversed %}     <p>{{ item|ctype }} - {{ item.title }} - {{ item.pubdat }}</p>     {% endfor %} </div> 

Bonus - combine objects with different field names

Sometimes it can be required to create these kind of feeds with existing/3rd party models. In that case you don't have the same fieldname for all models to sort by.

DATE_FIELD_MAPPING = {     Tender: 'pubdat',     Job: 'publish_date',     News: 'created', }  def date_key_mapping(obj):     return getattr(obj, DATE_FIELD_MAPPING[type(obj)])   def feed(request):     object_list = sorted(chain(         Tender.objects.all(),         Job.objects.all(),         News.objects.all()     ), key=date_key_mapping) 

Answers 4

I cannot test it right now, but you should create a model like:

class Post(models.Model):      pubdat = models.DateTimeField(default=timezone.now)     tender = models.ForeignKey('Tender')     job = models.ForeignKey('Job')     news = models.ForeignKey('News') 

Then, each time a new model is created, you create a Post as well and relate it to the Tender/Job/News. You should relate each post to only one of the three models.

Create a serializer for Post with indented serializers for Tender, Job and News.

Sorry for the short answer. If you think it can work for your problem, i'll write more later.

Answers 5

Do I make a separate model e.g. class Post and then use signal to create a new post whenever a new object is created from Tender, or Job, or News? I really hope there is a better way to achieve this. Or do I use multi-table inheritance?

I don't want to show each of the model objects separately at the same page. But like feeds of facebook or any other social media.

I personally don't see anything wrong using another model, IMHO its even preferable to use another model, specially when there is an app for that.

Why? Because I would never want to rewrite code for something which can be achieved by extending my current code. You are over-engineering this problem, and if not now, you are gonna suffer later.

Answers 6

An alternative solution would be to use Django haystack:

It allows you to search through unrelated models. It's more work than the other solutions but it's efficient (1 fast query) and you'll be able to easily filter your listing too.


In your case, you will want to define pubdate in all the search indexes.

Read More

Thursday, April 21, 2016

Base Template in reusable Django App

Leave a Comment

I want create a reusable app from a custom project.

The part I want to move to a reusable app serves whole pages and uses this at the top:

{% extends "myproject/base.html" %}

Now I am unsure what base template to use. In the new reusable app, I don't want to use myproject since this is custom code which I can't share.

I tried to get help from the great django docs, but could not find a solution - Forgive me if I was blind :-)

What to write here in a reusable app?

{% extends ??????? %}

3 Answers

Answers 1

You need to store those templates within the app folder.

Assuming you have a structure like myproject/myreusableapp you would create a templates folder inside myreusableapp folder and store your base.html file there.

You would then extend that file {% extends "myreusableapp/base.html" %}

Answers 2

Unfortunately django does not offer an official convention here.

There is a third party documentation project which defines these rules for reusable templates:

https://django-reusable-app-docs.readthedocs.org/en/latest/apps/templates.html

The issue to get a convention in the official django docs is open since 2012:

https://code.djangoproject.com/ticket/19106

Here is my ticket, which was closed as duplicate of above:

https://code.djangoproject.com/ticket/26501

... I hope an agreement will be found in the future.

Answers 3

I do it this way :

\project | +-\main | | | +-\template | | | | | +-\mainapp | |   | | |   +-base.html | |   +-index.html | |   +-... | | | +-\static |   | |   +-css |   +-js |   +-pics |   +-... | +-\otherapp   |   +-... 

Where main is an app for standard operations (pager, login,...) and has the base templates and static for in it. Note that templates are stored in main/templates/main/.

In project settings.py, don't forget to register main/static in the STATICFILES_DIRS.

Then you can deploy using {% extends 'main/base.html' %}.

Read More