How to add some context to each entry of a django

2019-07-12 10:56发布

问题:

I use Django 1.8.4 with Python 3.4

I have a model for tournaments that defines a method which returns a string if a subscription is forbidden.

class Tournament(models.Model):
    name = models.CharField(max_length=200, null=True, blank=True)
    subscriptions = models.ManyToManyField('ap_users.Profile')
    is_subscription_open = models.BooleanField(default=True)
    # ...

    def why_subscription_impossible(self, request):
        if not request.user.profile.is_profile_complete():
            return 'Your profile is not complete'
        elif not self.is_subscription_open:
            return 'Subscriptions are closed'
        elif <another_condition>:
            return 'Another error message'

        return None

I want to display the list of tournaments, using a generic ListView, and I want to use the result of the method to modify the way it is displayed:

<table class="table">
    <thead>
        <td>Tournament</td>
        <td>Subscription</td>
    </thead>
    {% for tournament in tournament_list %}
        <tr>
            <td>{{ tournament.name }}</td>
            <td>
                {% if tournament.why_subscription_impossible %}
                    {{ tournament.why_subscription_impossible }}
                {% else %}
                    <a href="{% url 'ap_tournament:subscribe' tournament.id %}">Subscribe</a>
                {% endif %}
            </td>
        </tr>
    {% endfor %}
</table>

The view is a class based generic view inherited from generic.ListView.

class IndexView(generic.ListView):
    template_name = 'ap_tournament/index.html'

    def get_queryset(self):
        return Tournament.objects.all()

The shown solution doesn't work, because I need to pass the current request, to get information about logged user. So I tried to add the result of the method to a context in the view

class IndexView(generic.ListView):
    template_name = 'ap_tournament/index.html'

    def get_queryset(self):
        return Tournament.objects.all()

    def get_context_data(self, **kwargs):
        context = super(IndexView, self).get_context_data(**kwargs)

        additional_ctx_info = []
        for tournament in self.get_queryset():
            additional_ctx_info.append({
                'reason_to_not_subscribe': tournament.why_subscription_impossible(self.request)
            })

        context['subscr_info'] = additional_ctx_info
        return context

Obviously, this doesn't work too. I don't know how to access to the subscr_info[n] with n the current index in the tournament_list. I know the forloop.counter0 to get the index, but I can't use it in the template (or I don't know how). I tried :

  • {{ subscr_info.forloop.counter0.reason_to_not_subscribe }}
  • {{ subscr_info.{{forloop.counter0}}.reason_to_not_subscribe }}

I also tried to annotate the QuerySet in get_queryset() view method and read about aggregate(), but I feel that works only with operations supported by the database (AVG, COUNT, MAX, etc.).

I also feels that using a filter or a template tag will not work in my case since I need to use the result of the method in a if tag.

Is there a better solution or a completely diffferent method to achieve what I want ?

回答1:

In your view, you could also do:

tournaments = self.get_queryset()
for tournament in tournaments:
    tournament.reason_to_not_subscribe = tournament.why_subscription_impossible(self.request)

Then add tournaments to the context.



回答2:

You have to create tournament_list in your views.py file, and set it depending on whether the user is logged and has the corresponding permissions.

If you need to count something, you can create the following Counter class :

class Counter:
    count = 0

    def increment(self):
        self.count += 1
        return ''

    def decrement(self):
        self.count -= 1
        return ''

You can then use it in your templates by calling {{ counter.increment }} and {{ counter.count }}. ({{ subscr_info.{{counter.count}}.reason_to_not_subscribe }} and don't forget to place {{ counter.increment }} in your loop.

However, a nicer workaround I used was to create a dictionnary containing both the main element and the additional information, i.e.

ctx_info = []
for tournament in self.get_queryset():
    ctx_info.append({
        'tournament': tournament
        'reason_to_not_subscribe': tournament.why_subscription_impossible(self.request)
    })

and then loop on ctx_info. It's cleaner, but I however do not know how this can be implemented within a ListView (which I never used)

By the way, your template contains {{ why_subscription_impossible }} instead of {{ tournament.why_subscription_impossible }}, I don't know if it was intended...