0

i'm working on a dashboard project (django + charts.js) that currently looks like this: enter image description here

currently, all of the charts are based on all the data in the database (approx 1.5 mil lines). however, i'd like these charts to be responsive to the dates indicated in the start date/end date HTML widget - e.g. show payment methods, taxi orders by the hour/day of week according to the start/end dates indicated.

my date_input method gets the start and end dates from the frontend, and queries my MySQL database according to the dates selected. these queries are then rendered to the context variable of date_input - however, i'd like the context variable in date_input to be available to all methods (each chart is rendered by a different method, based on the JsonResponse returned to different endpoints).

i've tried making context available as a global variable by defining it outside date_input as queryset = date_input(request) - NameError: name 'request' is not defined. i've looked into context processors, but that seems to be an option only if i didn't have a request (e.g. start/end date input from the frontend). i'm rather confused and would appreciate some help.

here's my views.py code:

def date_input(request):       
    if request.method == "POST": 
        context = {}

        start_date = request.POST.get("start_date", None) # type unicode
        end_date = request.POST.get("end_date", None)
        
        form = DateForm(request.POST or None)

        if form.is_valid():
            start_date = form.cleaned_data['start_date']
            end_date = form.cleaned_data['end_date']

            context['start_date'] = Order.objects.filter(member_created_time__gte=(start_date)) # this works
            context['end_date'] = Order.objects.filter(order_ended_time__lte=(end_date))

            print("weeeeeee!")
            print(context['start_date']) # type queryset
            print("----")
            print(context['end_date'])

            return render(request, 'date_input.html', context) # return JSON response to frontend
            # return JsonResponse({
            #     "context": float(context),
            # })

    else:
        form = DateForm()
        # print("form is not valid")

    # return JsonResponse({
    #     "form": float(form),
    # })
    return render(request, 'date_input.html', {
        "form": form
    })

dow_queryset = Member.objects.order_by('member_created_time').values_list('member_created_time', flat=True)

hkt = pytz.timezone('Asia/Shanghai')
dt_obj = [hkt.localize(datetime.datetime.fromtimestamp(x)) for x in dow_queryset]


def charts(request):  # pie chart for payment type
    queryset = Order.objects.order_by('payment').values(
       'payment').annotate(payment_type=Count('payment'))
    default = list(queryset.values_list('payment_type', flat=True))
    labels = list(queryset.values_list('payment', flat=True))
    
    return JsonResponse({
        'labels': labels,  # payment methods
        'default': default,  # payment methods
    })

def hourly(request): # bar chart to show taxi orders by hour of day
    hour_count = OrderedDict()
    hour_count['midnight'] = 0
    hour_count['early_morning'] = 0
    hour_count['morning'] = 0
    hour_count['afternoon'] = 0
    hour_count['night'] = 0

    for obj in dt_obj:
        if obj.hour == 0:
            hour_count['midnight'] += 1   
        elif obj.hour < 6:
            hour_count['early_morning'] += 1
        elif obj.hour < 12:
            hour_count['morning'] += 1  
        elif obj.hour < 19:
            hour_count['afternoon'] += 1
        else:
            hour_count['night'] += 1

    return JsonResponse({
        'labels_hour': list(hour_count.keys()),
        'data_hour': list(hour_count.values()),
    })

def dow(request): # bar chart to show no. of rides for each day fo the week 
    weekday_count = OrderedDict()
    weekday_count['monday'] = 0
    weekday_count['tuesday'] = 0
    weekday_count['wednesday'] = 0
    weekday_count['thursday'] = 0
    weekday_count['friday'] = 0
    weekday_count['saturday'] = 0
    weekday_count['sunday'] = 0

    for obj in dt_obj:
        if obj.weekday() == 0:
            weekday_count['monday'] += 1
        elif obj.weekday() == 1:
            weekday_count['tuesday'] += 1
        elif obj.weekday() == 2:
            weekday_count['wednesday'] += 1
        elif obj.weekday() == 3:
            weekday_count['thursday'] += 1
        elif obj.weekday() == 4:
            weekday_count['friday'] += 1
        elif obj.weekday() == 5:
            weekday_count['saturday'] += 1
        elif obj.weekday() == 6:
            weekday_count['sunday'] += 1
    
    return JsonResponse({
        'labels_day' : list(weekday_count.keys()),
        'data_day': list(weekday_count.values()),
    })

date_input.html (i have html files, titled charts.html, day.html, and hour.html for each chart respectively. the code is all quite similar in that they all use AJAX requests)

{% load static %}

{% block content %}

<div class="date">
    <br>
    <h3>Select to view distance and orders by timeframe</h3>
    <form name="date-input" method="POST" action="/date-input/">
        <label for="start">Start date:</label>
        <input type="date" id="start" name="start_date" value="2019-01-01" min="2019-01-01" max="2019-06-30">

        <label for="end">End date:</label>
        <input type="date" id="end" name="end_date" value="2019-01-01" min="2019-01-01" max="2019-07-01">

        <br>
        <br>
    </form>
</div>

{% endblock content %}

<script src="https://code.jquery.com/jquery-3.5.1.js"></script>
<script>

var start_date = Date.parse($("#start").val())/1000; // const can't be re-assigned or redeclared. Date() to convert datetime into epoch time
var end_date = Date.parse($("#end").val())/1000;  // divide by 1000 to get 10 digits (compatible with values in DB)

    function ajaxRequest (){
        var start_date = Date.parse($("#start").val())/1000;
        var end_date = Date.parse($("#end").val())/1000;

        $.ajax({                       // initialize an AJAX request
            type: "POST",
            url: '/date-input/', // calls data from /date-input/ endpoint
            data: {
                'start_date': start_date,       // add the order id to the POST parameters
                'end_date': end_date,
                'csrfmiddlewaretoken': "{{csrf_token}}",
            },
            success: function (data) {   // `data` is from `homepage` view function
                console.log("this block of code isn't useless!!")
                console.log(data.start_date)
                console.log(data.end_date)
            },
            error: function (data, xhr, status, error) {
                console.log("yikes")
            }
        });
    }

    $("#start, #end").change(function() { // calling select id
        start_date = Date.parse($("#start").val())/1000;
        end_date = Date.parse($("#end").val())/1000;  // get the selected subject ID from the HTML dropdown list 

        if (start_date < end_date) {
            ajaxRequest();
        } 
    });
        

</script>

cookiestarninja
  • 211
  • 4
  • 10
  • Do you want to set `start_date` and `end_date ` in `date_input()` method and then use it in `charts`, `hourly`, `dow` ...? – NKSM Mar 25 '21 at 09:20
  • @NKSM yes - specifically, i want `context['start_date']` and `context['end_date']` to be accessible in `charts`, `hourly`, and `dow`. the three methods will display the payment, hour, and datetime info associated with the queries that match the specified start/end date. is that possible? i'm looking at this: https://stackoverflow.com/questions/36961214/how-to-pass-argument-on-custom-context-processors-py, but i'd probably have to pass in `request.session['start_date']` and `request.session['end_date']` to context processors, if thats possible? – cookiestarninja Mar 25 '21 at 09:23
  • Yes, you can use it. But this is not context, you store your variables in session. – NKSM Mar 25 '21 at 09:36

2 Answers2

0

You can use Session Django Docs.

Edit the MIDDLEWARE setting and make sure it contains:

MIDDLEWARE = [
    ...
    'django.contrib.sessions.middleware.SessionMiddleware',
    ...
]

And in your view then:


def date_input(request):       
    if request.method == "POST": 
        context = {}

        start_date = request.POST.get("start_date", None) # type unicode
        end_date = request.POST.get("end_date", None)
        
        form = DateForm(request.POST or None)

        if form.is_valid():
            start_date = form.cleaned_data['start_date']
            end_date = form.cleaned_data['end_date']
            #  ADD your start_date and end_date to session
            request.session['start_date'] = start_date.strftime("%d/%m/%Y")
            request.session['end_date'] = end_date.strftime("%d/%m/%Y")
            # Here i would rename context variables to order_start_date and order_end_date to make more clear.
            # Because its QuerySet not date
            context['order_start_date'] = Order.objects.filter(member_created_time__gte=start_date) # this works
            context['order_end_date'] = Order.objects.filter(order_ended_time__lte=end_date)

            return render(request, 'date_input.html', context) # return JSON response to frontend
        else:
            print("form is not valid")  
    else:
        form = DateForm()
    return render(request, 'date_input.html', {
                  "form": form })

And any other view:


from datetime import datetime

def charts(request):  # pie chart for payment type
    try:
        start_date = datetime.strptime(request.session.get('start_date'), "%d/%m/%Y")
        end_data = datetime.strptime(request.session.get('end_date'), "%d/%m/%Y") 
    except:
        start_date = "default_value"
        end_date = "default_value"
    # filter
    queryset = Order.objects.order_by('payment').values(
       'payment').annotate(payment_type=Count('payment'))
    
    default = list(queryset.values_list('payment_type', flat=True))
    labels = list(queryset.values_list('payment', flat=True))
    
    return JsonResponse({
        'labels': labels,  # payment methods
        'default': default,  # payment methods
    })

NKSM
  • 5,422
  • 4
  • 25
  • 38
  • does this mean that `context['start_date']` and `context['end_date']` in `date_input` are not used anymore + `queryset` in the `charts` method would require adding `.filter(member_created_time__gte=(start_date), order_ended_time__lte=(end_date))`? also i'm getting a `NameError for my date-input method - `global name 'start_date' is not defined`. – cookiestarninja Mar 25 '21 at 09:51
  • @cookiestarninja, yes, you can filter like this: .filter(member_created_time__gte=start_date) order_ended_time__lte=end_date). I edited my answer. – NKSM Mar 25 '21 at 10:02
  • @cookiestarninja, i renamed in context **start_date** and **end_date** to **order_start_date** and **order_end_date**. Because its `QuerySet` not `DateTimeField` – NKSM Mar 25 '21 at 10:09
  • @cookiestarninja, also you need to convert your datetime object to string before store it in session. I added this also in my answer. – NKSM Mar 25 '21 at 10:27
  • 1
    thanks for all your help! actually all my time data was loaded in epoch time so the original solution worked just fine! – cookiestarninja Mar 26 '21 at 08:40
0

Create a context processor. it is a function that takes the request object as an argument and returns a dictionary that will be added to the request context. It will make your object globally available. Example, in a file called context_processors in your app named 'app'

# app.context_processors.chart
def charts(request):
    return {'labels': yourquersethere}

Then register it, for it to be accessible

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        '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',
                'app.context_processors.chart',
            ],
        },
    },
]
mr blu
  • 400
  • 5
  • 8