Source code for examples.views

# -*- coding: utf-8 -*-

import os
import random
import math
import json

from django.shortcuts import (render, redirect, )
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings
from django.db.models import Avg
from django.views.generic import CreateView
from django.urls import reverse_lazy

from .models import (UserInfo, CommentOfMySite, )
from .forms import (CommentOfMySiteForm, )

if 'DJANGO_TEST' in os.environ:
    from django_ai.bayesian_networks.models import BayesianNetwork
    from django_ai.systems.spam_filtering.models import SpamFilter
    from django_ai.examples import metrics
else:  # pragma: no cover
    from bayesian_networks.models import BayesianNetwork
    from systems.spam_filtering.models import SpamFilter
    from examples import metrics


PAGES_COLORS = {
    "A": "green", "B": "deep-orange", "C": "blue-grey", "D": "brown",
    "E": "lime", "F": "light-blue", "G": "teal", "H": "deep-purple",
    "I": "indigo", "J": "red"
}

USERS = getattr(settings, "DJANGO_AI_EXAMPLES_USERINFO_SIZE", 200)


def another_page(besides):
    other_pages = list(set(PAGES_COLORS.keys()) - set(besides))
    return(random.choice(other_pages))


def a_page_of_type_X(request, page_type="A", user_id=random.randint(1, USERS)):
    """
    View that renders pages of type X.
    user_id is for mimicking the user logged in (request.user).
    """
    bn = BayesianNetwork.objects.get(name="Clustering (Example)")
    if not bn.is_inferred:
        return(
            render(
                request,
                template_name="examples/sample_page.html",
                context={"is_inferred": False}
            )
        )
    current_page = page_type
    avg_time_pages_X = "avg_time_pages_" + current_page.lower()
    user_info = UserInfo.objects.get(id=user_id)
    users_ids = UserInfo.objects.all().values_list("id", flat=True)\
        .order_by('-id')[:200]  # Prevents the browser becoming unresponsive
    cluster_avg_time_pages = \
        bn.metadata["clusters_means"][user_info.cluster_1][0]
    # Calculate the cluster mean for the avg time of this type of page.
    # It may differ in the case of type A with the cluster mean of bn.metadata
    # calculated at the moment of performing the inference
    cluster_avg_times = UserInfo.objects\
        .filter(cluster_1=user_info.cluster_1)\
        .aggregate(**{avg_time_pages_X: Avg(avg_time_pages_X)})
    #
    return(
        render(
            request,
            template_name="examples/sample_page.html",
            context={
                "is_inferred": True,
                "page_type": page_type,
                "page_color": PAGES_COLORS[page_type],
                "current_user": int(user_id),
                "users_ids": users_ids,
                "other_page": another_page(besides=current_page),
                "cluster": user_info.cluster_1,
                "user_avg_time_pages_of_this_type":
                    getattr(user_info, avg_time_pages_X),
                "cluster_avg_time_pages_of_this_type":
                    cluster_avg_times[avg_time_pages_X],
                "user_avg_time_pages": user_info.avg_time_pages,
                "cluster_avg_time_pages": cluster_avg_time_pages,
            }
        )
    )


[docs]def new_user(request): """ Mimmics creating a user by creating an UserInfo object. """ bn = BayesianNetwork.objects.get(name="Clustering (Example)") new_user = UserInfo.objects.create( sex=random.choice([0, 1]), age=math.floor(random.gauss(30, 2)), avg1=random.gauss(10, 5), cluster_1=bn.assign_cluster([0, 0]) ) # -> Increment the BN internal counter by 5 # You **SHOULD NOT** do this, as it will trigger eventually an inference # on the model inside of the user's "navigation" request cycle (if # BN.counter_threshold is set) bn.counter += 5 bn.save() # -> Instead, update the counter "directly" in the database: # BayesianNetwork.objects.filter(name="Clustering (Example)")\ # .update(counter=F("counter") + 5) # and / or schedule a model recalculation. return(redirect('page', page_type="A", user_id=new_user.id))
METRICS_PIPELINE = { m: metrics.__dict__[m] for m in metrics.__dict__ if m.startswith("metric") }
[docs]@csrf_exempt def process_metrics(request, verbose=True): """ Minimal implementation of a Metricps Pipeline. The CSRF exemption is because it is highly unlikely that an external site posts values to our localhost to mess with our AI :) """ if request.method == "POST": data = json.loads(request.body.decode()) if verbose: print(data, METRICS_PIPELINE) for metric_name, metric in METRICS_PIPELINE.items(): metric(data) # -> Increment the BN internal counter by 1 # No problem doing this, as it is outside of the user's "navigation" # request cycle: bn = BayesianNetwork.objects.get(name="Clustering (Example)") bn.counter += 1 bn.save() # You could also update BN internal counter "directly", # without .save(): # BayesianNetwork.objects.filter(name="Clustering (Example)")\ # .update(counter=F("counter") + 1) # and / or schedule a model recalculation. return(HttpResponse(status=204)) else: return(HttpResponse(status=400))
class MetricsMixin(object): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Usage metrics bn = BayesianNetwork.objects.get(name="Clustering (Example)") context['bn'] = bn user_info = UserInfo.objects.get(id=self.get_user_id()) if bn.is_inferred: cluster_avg_time_pages = \ bn.metadata["clusters_means"][user_info.cluster_1][0] context['cluster_avg_time_pages'] = cluster_avg_time_pages return context
[docs]class CommentsOfMySiteView(MetricsMixin, CreateView): template_name = "examples/comments_of_my_site.html" form_class = CommentOfMySiteForm user_id = None def get_user_id(self): self.user_id = int(self.kwargs.get('user_id', random.randint(1, USERS))) return(self.user_id) def get_success_url(self): return(reverse_lazy('comments-of-my-site', kwargs={'user_id': self.get_user_id()})) def form_valid(self, form): # Increment the internal counter of the Spam Filter and # eventually trigger a recalculation of the model # BEWARE: This is inside the User's navigation request # cycle and is for demostrating purposes. You should # schedule a model update outside the cycle in production. sf_name = form.Meta.model.SPAM_FILTER spam_filter = SpamFilter.objects.get(name=sf_name) spam_filter.counter += 1 spam_filter.save() return(super(CommentsOfMySiteView, self).form_valid(form)) def get_initial(self): return({'user_id': self.get_user_id()}) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) spam_filter = SpamFilter.objects.get( name=CommentOfMySite.SPAM_FILTER) context['spam_filter'] = spam_filter context['is_inferred'] = (spam_filter.is_inferred and context['bn'].is_inferred) context['page_type'] = "C" context['page_color'] = PAGES_COLORS["C"] context['current_user'] = self.get_user_id() users_ids = UserInfo.objects.all().values_list("id", flat=True)\ .order_by('-id')[:200] # Prevents becoming unresponsive context['users_ids'] = users_ids context['users_dropdown_target'] = "comments-of-my-site" context['latest_comments'] = \ CommentOfMySite.objects.all().order_by("-id")[:10] return context