# -*- coding: utf-8 -*-
# This file is part of Shuup.
#
# Copyright (c) 2012-2021, Shuup Commerce Inc. All rights reserved.
#
# This source code is licensed under the OSL-3.0 license found in the
# LICENSE file in the root directory of this source tree.
from __future__ import unicode_literals
from django.conf import settings
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import activate, get_language, ugettext_lazy as _
from parler.models import TranslatableModel, TranslatedFields
from reversion.models import Version
from shuup.gdpr.utils import get_active_consent_pages
from shuup.simple_cms.models import Page
from shuup.utils.i18n import lang_lru_cache
GDPR_ANONYMIZE_TASK_TYPE_IDENTIFIER = "gdpr_anonymize"
@lang_lru_cache
[docs]def get_setting(shop):
instance, created = GDPRSettings.objects.get_or_create(shop=shop)
if created or not instance.safe_translation_getter("cookie_banner_content"):
instance.set_default_content()
return instance
@python_2_unicode_compatible
[docs]class GDPRSettings(TranslatableModel):
shop = models.OneToOneField("shuup.Shop", related_name="gdpr_settings", on_delete=models.CASCADE)
enabled = models.BooleanField(
default=False, verbose_name=_("enabled"), help_text=_("Define if the GDPR is active.")
)
skip_consent_on_auth = models.BooleanField(
default=False,
verbose_name=_("skip consent on login"),
help_text=_("Do not require consent on login when GDPR is activated."),
)
privacy_policy_page = models.ForeignKey(
on_delete=models.CASCADE,
to="shuup_simple_cms.Page",
null=True,
verbose_name=_("privacy policy page"),
help_text=_(
"Choose your privacy policy page here. If this page changes, customers will be " "prompted for new consent."
),
)
consent_pages = models.ManyToManyField(
"shuup_simple_cms.Page",
verbose_name=_("consent pages"),
related_name="consent_settings",
help_text=_(
"Choose pages here which are being monitored for customer consent. If any of these pages change, "
"the customer is being prompted for a new consent."
),
)
translations = TranslatedFields(
cookie_banner_content=models.TextField(
blank=True,
verbose_name=_("cookie banner content"),
help_text=_("The text to be presented to users in a pop-up warning."),
),
cookie_privacy_excerpt=models.TextField(
blank=True,
verbose_name=_("cookie privacy excerpt"),
help_text=_("The summary text to be presented about cookie privacy."),
),
auth_consent_text=models.TextField(
blank=True,
verbose_name=_("login consent text"),
help_text=_(
"Shown in login page between the form and the button. "
"Optional, but should be considered when the consent on login is disabled."
),
),
)
class Meta:
verbose_name = _("GDPR settings")
verbose_name_plural = _("GDPR settings")
def __str__(self):
return _("GDPR for {}").format(self.shop)
[docs] def set_default_content(self):
language = get_language()
for code, name in settings.LANGUAGES:
activate(code)
self.set_current_language(code)
self.cookie_banner_content = settings.SHUUP_GDPR_DEFAULT_BANNER_STRING
self.cookie_privacy_excerpt = settings.SHUUP_GDPR_DEFAULT_EXCERPT_STRING
self.save()
self.set_current_language(language)
activate(language)
@classmethod
[docs] def get_for_shop(cls, shop):
return get_setting(shop)
@python_2_unicode_compatible
[docs]class GDPRCookieCategory(TranslatableModel):
shop = models.ForeignKey(on_delete=models.CASCADE, to="shuup.Shop", related_name="gdpr_cookie_categories")
always_active = models.BooleanField(default=False, verbose_name=_("always active"))
default_active = models.BooleanField(
verbose_name=_("active by default"),
default=False,
help_text=_("whether this cookie category is active by default"),
)
cookies = models.TextField(
verbose_name=_("cookies used"),
help_text=_(
"Comma separated list of cookies names, prefix or suffix "
"that will be included in this category, "
"e.g. _ga, mysession, user_c_"
),
)
translations = TranslatedFields(
name=models.CharField(max_length=64, verbose_name=_("name")),
how_is_used=models.TextField(
verbose_name=_("how we use"),
help_text=_("Describe the purpose of this category of cookies and how it is used."),
blank=True,
),
)
class Meta:
verbose_name = _("GDPR cookie category")
verbose_name_plural = _("GDPR cookie categories")
def __str__(self):
return _("GDPR cookie category for {}").format(self.shop)
@python_2_unicode_compatible
[docs]class GDPRUserConsent(models.Model):
created_on = models.DateTimeField(auto_now_add=True, editable=False, db_index=True, verbose_name=_("created on"))
shop = models.ForeignKey(on_delete=models.CASCADE, to="shuup.Shop", related_name="gdpr_consents", editable=False)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
related_name="gdpr_consents",
on_delete=models.PROTECT,
editable=False,
)
documents = models.ManyToManyField(
"GDPRUserConsentDocument", verbose_name=_("consent documents"), blank=True, editable=False
)
class Meta:
verbose_name = _("GDPR user consent")
verbose_name_plural = _("GDPR user consents")
@classmethod
[docs] def ensure_for_user(cls, user, shop, consent_documents):
documents = []
for page in consent_documents:
Page.create_initial_revision(page)
version = Version.objects.get_for_object(page).first()
consent_document = GDPRUserConsentDocument.objects.create(page=page, version=version)
documents.append(consent_document)
# ensure only one consent exists for this user in this shop
consent = cls.objects.filter(shop=shop, user=user).first()
if consent:
consents = cls.objects.filter(shop=shop, user=user).order_by("-created_on")
if consents.count() > 1:
# There are multiple consents, remove excess
ids = [c.id for c in consents.all() if c.id != consent.id]
cls.objects.filter(pk__in=ids).delete()
else:
consent = cls.objects.create(shop=shop, user=user)
consent.documents.set(documents)
return consent
@classmethod
[docs] def get_for_user(cls, user, shop):
return cls.objects.filter(user=user, shop=shop).order_by("-created_on").first()
[docs] def should_reconsent(self, shop, user):
consent_pages_ids = set([page.id for page in get_active_consent_pages(shop)])
page_ids = set([doc.page.id for doc in self.documents.all()])
if consent_pages_ids != page_ids:
return True
# all matches, check versions
for consent_document in self.documents.all():
version = Version.objects.get_for_object(consent_document.page).first()
if consent_document.version != version:
return True
return False
[docs] def should_reconsent_to_page(self, page):
version = Version.objects.get_for_object(page).first()
return not self.documents.filter(page=page, version=version).exists()
def __str__(self):
return _("GDPR user consent in {} for user {} in shop {}").format(self.created_on, self.user, self.shop)
@python_2_unicode_compatible
[docs]class GDPRUserConsentDocument(models.Model):
page = models.ForeignKey(on_delete=models.CASCADE, to="shuup_simple_cms.Page")
version = models.ForeignKey(on_delete=models.CASCADE, to=Version)
def __str__(self):
return _("GDPR user consent document for {} (Version: {})").format(self.page, self.version)