зеркало из https://github.com/mozilla/kitsune.git
Merge pull request #4896 from mozilla/zendesk
This commit is contained in:
Коммит
ed0f1c1851
|
@ -30,6 +30,7 @@ Part 2: Developer's Guide
|
|||
search
|
||||
frontend
|
||||
browser_permissions
|
||||
zendesk
|
||||
notes
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# Zendesk integration
|
||||
|
||||
## Using `requests` to query the API
|
||||
|
||||
During development being able to query the API manually to fetch details about field IDs,
|
||||
user statuses,
|
||||
and so on is very useful.
|
||||
|
||||
To do so use a snippet of code like the following in `./manage.py shell_plus`:
|
||||
|
||||
```python
|
||||
import requests
|
||||
base = f"https://{settings.ZENDESK_SUBDOMAIN}.zendesk.com/api/v2/"
|
||||
auth = requests.auth.HTTPBasicAuth(settings.ZENDESK_USER_EMAIL+"/token", settings.ZENDESK_API_TOKEN)
|
||||
|
||||
requests.get(base+"foobar", auth=auth).json()
|
||||
|
||||
requests.post(base+"barfoo", auth=auth, json={}).json()
|
||||
```
|
|
@ -3,3 +3,6 @@ from django.apps import AppConfig
|
|||
|
||||
class CustomerCareConfig(AppConfig):
|
||||
name = "kitsune.customercare"
|
||||
|
||||
def ready(self):
|
||||
from kitsune.customercare import signals # noqa
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _lazy
|
||||
|
||||
from kitsune.customercare.zendesk import ZendeskClient, CATEGORY_CHOICES, OS_CHOICES
|
||||
|
||||
|
||||
class ZendeskForm(forms.Form):
|
||||
"""Form for submitting a ticket to Zendesk."""
|
||||
|
||||
product = forms.CharField(disabled=True, widget=forms.HiddenInput)
|
||||
category = forms.ChoiceField(
|
||||
label=_lazy("What do you need help with?"), choices=CATEGORY_CHOICES
|
||||
)
|
||||
os = forms.ChoiceField(
|
||||
label=_lazy("What operating system does your device use?"),
|
||||
choices=OS_CHOICES,
|
||||
required=False,
|
||||
)
|
||||
subject = forms.CharField(label=_lazy("Subject"), required=False)
|
||||
description = forms.CharField(label=_lazy("Description of issue"), widget=forms.Textarea())
|
||||
|
||||
def __init__(self, *args, product, **kwargs):
|
||||
kwargs.update({"initial": {"product": product.slug}})
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def send(self, user):
|
||||
client = ZendeskClient()
|
||||
return client.create_ticket(user, **self.cleaned_data)
|
|
@ -0,0 +1,35 @@
|
|||
from django.contrib.auth.models import User
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from kitsune.customercare.tasks import update_zendesk_user
|
||||
from kitsune.users.models import Profile
|
||||
|
||||
|
||||
@receiver(
|
||||
post_save, sender=User, dispatch_uid="customercare.signals.on_save_update_zendesk_user.User"
|
||||
)
|
||||
@receiver(
|
||||
post_save,
|
||||
sender=Profile,
|
||||
dispatch_uid="customercare.signals.on_save_update_zendesk_user.Profile",
|
||||
)
|
||||
def on_save_update_zendesk_user(sender, instance, update_fields=None, **kwargs):
|
||||
# TODO: dedupe signals, so calling
|
||||
# ```
|
||||
# user.profile.save()
|
||||
# user.save()
|
||||
# ```
|
||||
# doesn't update the user in zendesk twice
|
||||
|
||||
user = instance
|
||||
if sender == Profile:
|
||||
user = instance.user
|
||||
if update_fields and len(update_fields) == 1 and "zendesk_id" in update_fields:
|
||||
# do nothing if the only thing updated is the zendesk_id
|
||||
return
|
||||
|
||||
try:
|
||||
if user.profile.zendesk_id:
|
||||
update_zendesk_user.delay(user.pk)
|
||||
except Profile.DoesNotExist:
|
||||
pass
|
|
@ -0,0 +1,23 @@
|
|||
from celery import task
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from kitsune.customercare.zendesk import ZendeskClient
|
||||
|
||||
|
||||
@task
|
||||
def update_zendesk_user(user_id: int) -> None:
|
||||
user = User.objects.get(pk=user_id)
|
||||
if user.profile.zendesk_id:
|
||||
zendesk = ZendeskClient()
|
||||
zendesk.update_user(user)
|
||||
|
||||
|
||||
@task
|
||||
def update_zendesk_identity(user_id: int, email: str) -> None:
|
||||
user = User.objects.get(pk=user_id)
|
||||
zendesk_user_id = user.profile.zendesk_id
|
||||
|
||||
# fetch identity id
|
||||
if zendesk_user_id:
|
||||
zendesk = ZendeskClient()
|
||||
zendesk.update_primary_email(zendesk_user_id, email)
|
|
@ -0,0 +1,109 @@
|
|||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _lazy
|
||||
from zenpy import Zenpy
|
||||
from zenpy.lib.api_objects import Identity as ZendeskIdentity
|
||||
from zenpy.lib.api_objects import Ticket
|
||||
from zenpy.lib.api_objects import User as ZendeskUser
|
||||
|
||||
TICKET_FORM_ID = 360000417171
|
||||
|
||||
# See docs/zendesk.md for details about getting the valid choice values for each field:
|
||||
|
||||
PRODUCT_FIELD_ID = 360047198211
|
||||
|
||||
CATEGORY_FIELD_ID = 360047206172
|
||||
CATEGORY_CHOICES = [
|
||||
(None, _lazy("Select a topic")),
|
||||
("technical", _lazy("Technical")),
|
||||
("accounts", _lazy("Accounts & Login")),
|
||||
("payments", _lazy("Payment & Billing")),
|
||||
("troubleshooting", _lazy("Troubleshooting")),
|
||||
]
|
||||
|
||||
OS_FIELD_ID = 360018604871
|
||||
OS_CHOICES = [
|
||||
(None, _lazy("Select platform")),
|
||||
("win10", _lazy("Windows")),
|
||||
("android", _lazy("Android")),
|
||||
("linux", _lazy("Linux")),
|
||||
("mac", _lazy("Mac OS")),
|
||||
("win8", _lazy("Windows 8")),
|
||||
]
|
||||
|
||||
|
||||
class ZendeskClient(object):
|
||||
"""Client to connect to Zendesk API."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Initialize Zendesk API client."""
|
||||
creds = {
|
||||
"email": settings.ZENDESK_USER_EMAIL,
|
||||
"token": settings.ZENDESK_API_TOKEN,
|
||||
"subdomain": settings.ZENDESK_SUBDOMAIN,
|
||||
}
|
||||
self.client = Zenpy(**creds)
|
||||
|
||||
def _user_to_zendesk_user(self, user, include_email=True):
|
||||
fxa_uid = user.profile.fxa_uid
|
||||
id_str = user.profile.zendesk_id
|
||||
return ZendeskUser(
|
||||
id=int(id_str) if id_str else None,
|
||||
verified=True,
|
||||
email=user.email if include_email else "",
|
||||
name=user.profile.display_name,
|
||||
locale=user.profile.locale,
|
||||
user_fields={"user_id": fxa_uid},
|
||||
external_id=fxa_uid,
|
||||
)
|
||||
|
||||
def create_user(self, user):
|
||||
"""Given a Django user, create a user in Zendesk."""
|
||||
zendesk_user = self._user_to_zendesk_user(user)
|
||||
# call create_or_update to avoid duplicating users FxA previously created
|
||||
zendesk_user = self.client.users.create_or_update(zendesk_user)
|
||||
|
||||
user.profile.zendesk_id = str(zendesk_user.id)
|
||||
user.profile.save(update_fields=["zendesk_id"])
|
||||
|
||||
return zendesk_user
|
||||
|
||||
def update_user(self, user):
|
||||
"""Given a Django user, update a user in Zendesk."""
|
||||
zendesk_user = self._user_to_zendesk_user(user, include_email=False)
|
||||
zendesk_user = self.client.users.update(zendesk_user)
|
||||
return zendesk_user
|
||||
|
||||
def get_primary_email_identity(self, zendesk_user_id):
|
||||
"""Fetch the identity with the primary email from Zendesk"""
|
||||
|
||||
for identity in self.client.users.identities(id=zendesk_user_id):
|
||||
if identity.primary and identity.type == "email":
|
||||
return identity.id
|
||||
|
||||
def update_primary_email(self, zendesk_user_id, email):
|
||||
"""Update the primary email of the user."""
|
||||
identity_id = self.get_primary_email_identity(zendesk_user_id)
|
||||
self.client.users.identities.update(
|
||||
user=zendesk_user_id, identity=ZendeskIdentity(id=identity_id, value=email)
|
||||
)
|
||||
|
||||
def create_ticket(
|
||||
self, user, subject="", description="", product="", category="", os="", **kwargs
|
||||
):
|
||||
"""Create a ticket in Zendesk."""
|
||||
ticket = Ticket(
|
||||
subject=subject,
|
||||
comment={"body": description},
|
||||
ticket_form_id=TICKET_FORM_ID,
|
||||
custom_fields=[
|
||||
{"id": PRODUCT_FIELD_ID, "value": product},
|
||||
{"id": CATEGORY_FIELD_ID, "value": category},
|
||||
{"id": OS_FIELD_ID, "value": os},
|
||||
],
|
||||
)
|
||||
if user.profile.zendesk_id:
|
||||
# TODO: is this necessary if we're updating users as soon as they're updated locally?
|
||||
ticket.requester_id = self.update_user(user).id
|
||||
else:
|
||||
ticket.requester_id = self.create_user(user).id
|
||||
return self.client.tickets.create(ticket)
|
|
@ -24,10 +24,10 @@
|
|||
</div>
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro topic_metadata(topics, product_key=None) %}
|
||||
{% macro topic_metadata(topics, product=None, product_key=None) %}
|
||||
{% if not settings.READ_ONLY %}
|
||||
<section class="support-callouts mzp-l-content sumo-page-section--inner">
|
||||
<div class="card card--ribbon is-inverse">
|
||||
<div class="card card--ribbon is-inverse heading-is-one-line">
|
||||
<div class="card--details">
|
||||
<h3 class="card--title">
|
||||
<svg class="card--icon-sm" width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
|
@ -41,43 +41,29 @@
|
|||
|
||||
{{ _('Still need help?') }}
|
||||
</h3>
|
||||
<p class="card--desc">{{ _('We’re here for you. Post a question to our support forums and get answers from our community of experts.') }}</p>
|
||||
<p class="card--desc">
|
||||
{% if product and product.has_subscriptions %}
|
||||
{{ _('We’re here for you. Send a message to our support team and we\'ll be glad to help.') }}
|
||||
{% else %}
|
||||
{{ _('We’re here for you. Post a question to our support forums and get answers from our community of experts.') }}
|
||||
{% endif %}
|
||||
</p>
|
||||
{% if product_key %}
|
||||
{% set aaq_url=url('questions.aaq_step2', product_key=product_key) %}
|
||||
{% else %}
|
||||
{% set aaq_url=url('questions.aaq_step1') %}
|
||||
{% endif %}
|
||||
<a class="sumo-button primary-button button-lg"
|
||||
href={{ aaq_url }} data-event-label="Get community support">
|
||||
{{ _('Ask the Community') }}
|
||||
href={{ aaq_url }} data-event-label="Contact support">
|
||||
{% if product and product.has_subscriptions %}
|
||||
{{ _('Contact Support') }}
|
||||
{% else %}
|
||||
{{ _('Ask the Community') }}
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% if product.id in subscribed_products_ids %}
|
||||
<div class="mzp-l-content sumo-page-section--inner text-center">
|
||||
<h2 class="sumo-page-subheading">
|
||||
<svg width="24px" height="24px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.44 19.5">
|
||||
<path d="M18.88,0H19c.5.1.6.5.3,1.1L11.67,18.8c-.2.4-.39.7-.8.7-.2,0-.3,0-.4-.1s-.3-.2-.39-.4l-3-4.9c-.1-.2-.3-.4-.4-.6l-.6-.6c-.2-.2-.4-.3-.6-.5l-5-3.1a1.6,1.6,0,0,1-.4-.5.58.58,0,0,1,0-.6,1,1,0,0,1,.59-.5L18.38.2a1.59,1.59,0,0,1,.5-.2ZM6.47,10.9a2.65,2.65,0,0,1,.6.5l7.71-7.9-12,5.2,3.69,2.2ZM16,4.8l-7.7,7.8.1.1c.1.1.1.2.2.3s.1.2.2.3L11,16.8C10.88,16.7,16,4.8,16,4.8Z"/>
|
||||
</svg>
|
||||
|
||||
{{ _('Contact Us') }}
|
||||
</h2>
|
||||
<p class="sumo-page-intro">{{ _('Contact the Support Team for help.') }}</p>
|
||||
<a
|
||||
class="sumo-button primary-button button-lg"
|
||||
href="{{ settings.FXA_SUPPORT_FORM }}"
|
||||
data-event-category="link click"
|
||||
data-event-action="topic"
|
||||
data-event-label="Contact Support">
|
||||
{{ _('Ask a Question') }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="mzp-l-content">
|
||||
<hr />
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
|
|
|
@ -152,7 +152,7 @@
|
|||
{{ help_topics(topics) }}
|
||||
</div>
|
||||
|
||||
{{ topic_metadata(topics, product_key=product_key) }}
|
||||
{{ topic_metadata(topics, product=product, product_key=product_key) }}
|
||||
|
||||
{% if featured %}
|
||||
<section class="mzp-l-content mzp-l-content sumo-page-section--inner">
|
||||
|
|
|
@ -32,7 +32,6 @@ def _get_aaq_product_key(slug):
|
|||
def product_landing(request, slug):
|
||||
"""The product landing page."""
|
||||
product = get_object_or_404(Product, slug=slug)
|
||||
user = request.user
|
||||
|
||||
if request.is_ajax():
|
||||
# Return a list of topics/subtopics for the product
|
||||
|
@ -60,11 +59,6 @@ def product_landing(request, slug):
|
|||
"topics": topics_for(product=product, parent=None),
|
||||
"search_params": {"product": slug},
|
||||
"latest_version": latest_version,
|
||||
"subscribed_products_ids": (
|
||||
user.profile.products.all().values_list("id", flat=True)
|
||||
if user.is_authenticated
|
||||
else []
|
||||
),
|
||||
"featured": get_featured_articles(product, locale=request.LANGUAGE_CODE),
|
||||
},
|
||||
)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{% from 'includes/common_macros.html' import featured_articles, scam_banner %}
|
||||
|
||||
{% macro select_product(products) -%}
|
||||
<h1 class="sumo-page-heading">{{ _('Ask the Community') }}</h1>
|
||||
<h1 class="sumo-page-heading">{{ _('Contact Support') }}</h1>
|
||||
<h2 class="sumo-page-subheading">{{ _('Which product do you need help with?') }}</h2>
|
||||
<div class="sumo-page-section--inner">
|
||||
<div id="product-picker" class="sumo-card-grid stack-on-mobile" style="--cg-count: {{ products|length }};">
|
||||
|
@ -77,7 +77,7 @@
|
|||
<a class="progress--link" href="#" disabled>
|
||||
<span class="progress--link-inner">
|
||||
<span class="progress--dot"></span>
|
||||
<span class="progress--label">{{ _('Ask Question') }}</span>
|
||||
<span class="progress--label">{{ _('Get Support') }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
@ -87,9 +87,22 @@
|
|||
{% macro aaq_widget(request, location="aaq") %}
|
||||
{% set aaq_context = request.session.get("aaq_context") %}
|
||||
{% if aaq_context %}
|
||||
<div class="aaq-widget card elevation-01 text-center radius-md">
|
||||
<h2 class="card--title has-bottom-margin">{{ _('Ask the Community') }}</h2>
|
||||
{% if request.user.is_authenticated %}
|
||||
<div class="aaq-widget card is-inverse elevation-01 text-center radius-md">
|
||||
<h2 class="card--title has-bottom-margin">
|
||||
<svg class="card--icon-sm" width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||
<g transform="translate(2.000000, 1.878680)" stroke="#FFFFFF" stroke-width="2">
|
||||
<path d="M9,1.12132031 L2,1.12132031 C0.8954305,1.12132031 5.32907052e-15,2.01675081 5.32907052e-15,3.12132031 L5.32907052e-15,15.1213203 C5.32907052e-15,16.2258898 0.8954305,17.1213203 2,17.1213203 L11,17.1213203 L13,21.1213203 L15,17.1213203 L17,17.1213203 C18.1045695,17.1213203 19,16.2258898 19,15.1213203 L19,9.12132031"></path>
|
||||
<path d="M15.5,0.621320312 C16.3284271,-0.207106783 17.6715729,-0.207106769 18.5,0.621320344 C19.3284271,1.44974746 19.3284271,2.79289318 18.5,3.62132031 L11,11.1213203 L7,12.1213203 L8,8.12132031 L15.5,0.621320312 Z"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
{{ _('Still need help?') }}
|
||||
</h2>
|
||||
{% if aaq_context.has_subscriptions %}
|
||||
<p>{{ _('Send a message to our support team.') }}</p>
|
||||
{% elif request.user.is_authenticated %}
|
||||
<p>{{ _('Continue to post your question and get help.') }}</p>
|
||||
{% else %}
|
||||
<p>{{ _('Sign in/up to post your question and get help.') }}</p>
|
||||
|
@ -103,19 +116,27 @@
|
|||
href="{{ next_step }}"
|
||||
data-event-category="link click"
|
||||
data-event-action="{{ location }}"
|
||||
data-event-label="aaq widget">{{ _('Ask Now') }}</a>
|
||||
data-event-label="aaq widget">
|
||||
{% if aaq_context.has_subscriptions %}
|
||||
{{ _('Contact Support') }}
|
||||
{% else %}
|
||||
{{ _('Ask Now') }}
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro explore_solutions(product, search_box, featured, topics, request) -%}
|
||||
{% macro explore_solutions(product, search_box, featured, topics, request, has_subscriptions=True) -%}
|
||||
{% set search_params = {'product': product.product} %}
|
||||
<section class="sumo-page-section question-masthead shade-bg">
|
||||
<div class="mzp-l-content">
|
||||
|
||||
{{ progress_bar(2) }}
|
||||
|
||||
{{ scam_banner() }}
|
||||
{% if not has_subscriptions %}
|
||||
{{ scam_banner() }}
|
||||
{% endif %}
|
||||
|
||||
<div class="sumo-l-two-col sidebar-on-right align-center cols-on-medium">
|
||||
<div class="sumo-l-two-col--main home-search-section--content">
|
||||
|
|
|
@ -22,9 +22,11 @@ we can compute the edit-title URL.
|
|||
<article class="main mzp-l-content sumo-page-section--inner">
|
||||
{% if current_step %}
|
||||
{{ progress_bar(current_step, product_key=current_product.key) }}
|
||||
{% endif %}
|
||||
|
||||
{{ scam_banner() }}
|
||||
{% if current_step > 1 and not has_subscriptions %}
|
||||
{{ scam_banner() }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% block formwrap %}
|
||||
<img class="page-heading--logo" src="{{ image_for_product(current_product.get('product', '')) }}" alt="{{ current_product.get('name', '') }} logo" />
|
||||
|
@ -46,23 +48,23 @@ we can compute the edit-title URL.
|
|||
{% endblock %}
|
||||
<div class="sumo-l-two-col">
|
||||
<aside class="sumo-l-two-col--sidebar">
|
||||
{% if form %}
|
||||
<div class="card has-moz-headings is-in-sidebar is-callout-bg text-center large-only">
|
||||
<img class="card--img" src="{{ STATIC_URL }}sumo/img/Mozilla-Heads-Keith-Negley-180628__400.png" alt="Illustration of community" />
|
||||
<div class="card--details">
|
||||
<h3 class="card--title">{{ _('Our Community is here to help') }}</h3>
|
||||
<p class="card--desc">{{ _('Kindness is at the heart of our community. Our volunteers are happy to share their time and Firefox knowledge with you.') }}</p>
|
||||
<p><strong><a href="{{ url('landings.get_involved') }}">{{ _('Learn More') }}</a></strong></p>
|
||||
</div>
|
||||
</div>
|
||||
{% if form and not has_subscriptions %}
|
||||
<div class="card has-moz-headings is-in-sidebar is-callout-bg text-center large-only">
|
||||
<img class="card--img" src="{{ STATIC_URL }}sumo/img/Mozilla-Heads-Keith-Negley-180628__400.png" alt="Illustration of community" />
|
||||
<div class="card--details">
|
||||
<h3 class="card--title">{{ _('Our Community is here to help') }}</h3>
|
||||
<p class="card--desc">{{ _('Kindness is at the heart of our community. Our volunteers are happy to share their time and Firefox knowledge with you.') }}</p>
|
||||
<p><strong><a href="{{ url('landings.get_involved') }}">{{ _('Learn More') }}</a></strong></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="large-only">
|
||||
<h3 class="sumo-card-heading">
|
||||
<img class="card--icon-sm" src="{{ STATIC_URL }}protocol/img/icons/highlight.svg" alt="Helpful Tip icon" />
|
||||
{{ _('Helpful Tip!')}}
|
||||
</h3>
|
||||
<p>{{ _('Follow through. Sometimes, our volunteers would ask you for more information or to test out certain scenarios. The sooner you can do this, the sooner they would know how to fix it.')}}
|
||||
</div>
|
||||
<div class="large-only">
|
||||
<h3 class="sumo-card-heading">
|
||||
<img class="card--icon-sm" src="{{ STATIC_URL }}protocol/img/icons/highlight.svg" alt="Helpful Tip icon" />
|
||||
{{ _('Helpful Tip!')}}
|
||||
</h3>
|
||||
<p>{{ _('Follow through. Sometimes, our volunteers would ask you for more information or to test out certain scenarios. The sooner you can do this, the sooner they would know how to fix it.')}}
|
||||
</div>
|
||||
{% endif %}
|
||||
</aside>
|
||||
<article class="sumo-l-two-col--main">
|
||||
|
@ -77,21 +79,22 @@ we can compute the edit-title URL.
|
|||
<form id="question-form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<p class="sumo-page-intro">
|
||||
|
||||
{% trans %}
|
||||
Be nice. Our volunteers are Mozilla users just like you,
|
||||
who take the time out of their day to help.
|
||||
{% endtrans %}
|
||||
</p>
|
||||
<div class="info card shade-bg highlight mb">
|
||||
{% trans %}
|
||||
Be descriptive. Saying "Playing video on YouTube is
|
||||
always choppy" will help our volunteers identify
|
||||
your problem better than saying "Something is
|
||||
wrong" or "Firefox is broken".
|
||||
{% endtrans %}
|
||||
</div>
|
||||
{% if not has_subscriptions %}
|
||||
<p class="sumo-page-intro">
|
||||
{% trans %}
|
||||
Be nice. Our volunteers are Mozilla users just like you,
|
||||
who take the time out of their day to help.
|
||||
{% endtrans %}
|
||||
</p>
|
||||
<div class="info card shade-bg highlight mb">
|
||||
{% trans %}
|
||||
Be descriptive. Saying "Playing video on YouTube is
|
||||
always choppy" will help our volunteers identify
|
||||
your problem better than saying "Something is
|
||||
wrong" or "Firefox is broken".
|
||||
{% endtrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% for field in form.hidden_fields() %}
|
||||
{{ field|safe }}
|
||||
|
@ -100,20 +103,22 @@ we can compute the edit-title URL.
|
|||
{% set li_class='' %}
|
||||
{% for field in form.visible_fields() if not field.name == 'notifications' %}
|
||||
|
||||
{% if field.name == 'ff_version' or field.name == 'device' %}
|
||||
<li class="system-details-info show">
|
||||
<p>
|
||||
{{ _("We've made some educated guesses about your current browser and operating system.") }}
|
||||
<a href="#show-details" class="show">{{ _('Show details »')|safe }}</a>
|
||||
<a href="#hide-details" class="hide hide-until-expanded">{{ _('Hide details »')|safe }}</a>
|
||||
</p>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if field.name == 'ff_version' or field.name == 'device' or field.name == 'os' or field.name == 'plugins' %}
|
||||
{% set li_class='details' %}
|
||||
{% if not has_subscriptions %}
|
||||
{% if field.name == 'ff_version' or field.name == 'device' %}
|
||||
<li class="system-details-info show">
|
||||
<p>
|
||||
{{ _("We've made some educated guesses about your current browser and operating system.") }}
|
||||
<a href="#show-details" class="show">{{ _('Show details »')|safe }}</a>
|
||||
<a href="#hide-details" class="hide hide-until-expanded">{{ _('Hide details »')|safe }}</a>
|
||||
</p>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if field.name == 'ff_version' or field.name == 'device' or field.name == 'os' or field.name == 'plugins' %}
|
||||
{% set li_class='details' %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<li class="{{ li_class }} {% if field.errors %}has-error invalid{% endif %} cf">
|
||||
{{ field.label_tag()|safe }}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "questions/includes/question_editing_frame.html" %}
|
||||
{% from "questions/includes/aaq_macros.html" import explore_solutions %}
|
||||
{% from "questions/includes/aaq_macros.html" import select_product %}
|
||||
{% set title = _('Ask a Question') %}
|
||||
{% set title = _('Get Support') %}
|
||||
{% set no_headline = True %}
|
||||
{% set hide_locale_switcher = True %}
|
||||
{% set meta = [('robots', 'noindex')] %}
|
||||
|
@ -20,15 +20,23 @@
|
|||
|
||||
{% block contentwrap %}
|
||||
{% if current_step == 2 %}
|
||||
{{ explore_solutions(current_product, search_box, featured, topics, request) }}
|
||||
{{ explore_solutions(current_product, search_box, featured, topics, request, has_subscriptions) }}
|
||||
{% else %}
|
||||
{{ super() }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block major_detail_instructions %}
|
||||
<h2 class="sumo-page-heading">{{ _('Ask your question') }}</h2>
|
||||
<h2 class="sumo-page-heading">
|
||||
{% if has_subscriptions %}
|
||||
{{ _('Get support') }}
|
||||
{% else %}
|
||||
{{ _('Ask your question') }}
|
||||
{% endif %}
|
||||
</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block submit_button_value %}{{ _('Post Question') }}{% endblock %}
|
||||
{% block submit_button_value %}
|
||||
{{ _('Submit') }}
|
||||
{% endblock %}
|
||||
{% block submit_button_attrs %}data-event-category="Ask A Question Flow" data-event-action="step 3 link"{% endblock %}
|
||||
|
|
|
@ -25,11 +25,14 @@ from django.utils.translation import ugettext_lazy as _lazy
|
|||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_GET, require_http_methods, require_POST
|
||||
from django_user_agents.utils import get_user_agent
|
||||
from sentry_sdk import capture_exception
|
||||
from taggit.models import Tag
|
||||
from tidings.events import ActivationRequestFailed
|
||||
from tidings.models import Watch
|
||||
from zenpy.lib.exception import APIException
|
||||
|
||||
from kitsune.access.decorators import login_required, permission_required
|
||||
from kitsune.customercare.forms import ZendeskForm
|
||||
from kitsune.flagit.models import FlaggedObject
|
||||
from kitsune.products.models import Product, Topic
|
||||
from kitsune.questions import config
|
||||
|
@ -493,10 +496,11 @@ def aaq(request, product_key=None, category_key=None, step=1):
|
|||
except Product.DoesNotExist:
|
||||
raise Http404
|
||||
has_public_forum = product.questions_enabled(locale=request.LANGUAGE_CODE)
|
||||
has_subscriptions = product.has_subscriptions
|
||||
request.session["aaq_context"] = {
|
||||
"key": product_key,
|
||||
"has_public_forum": has_public_forum,
|
||||
"has_subscriptions": product.has_subscriptions,
|
||||
"has_subscriptions": has_subscriptions,
|
||||
}
|
||||
|
||||
context = {
|
||||
|
@ -506,6 +510,9 @@ def aaq(request, product_key=None, category_key=None, step=1):
|
|||
"host": Site.objects.get_current().domain,
|
||||
}
|
||||
|
||||
if step > 1:
|
||||
context["has_subscriptions"] = has_subscriptions
|
||||
|
||||
if step == 2:
|
||||
context["featured"] = get_featured_articles(product, locale=request.LANGUAGE_CODE)
|
||||
context["topics"] = topics_for(product, parent=None)
|
||||
|
@ -526,6 +533,35 @@ def aaq(request, product_key=None, category_key=None, step=1):
|
|||
|
||||
return HttpResponseRedirect(path)
|
||||
|
||||
if has_subscriptions:
|
||||
zendesk_form = ZendeskForm(data=request.POST or None, product=product)
|
||||
context["form"] = zendesk_form
|
||||
|
||||
if zendesk_form.is_valid():
|
||||
try:
|
||||
zendesk_form.send(request.user)
|
||||
|
||||
messages.add_message(
|
||||
request,
|
||||
messages.SUCCESS,
|
||||
_(
|
||||
"Done! Your message was sent to Mozilla Support, "
|
||||
"thank you for reaching out. "
|
||||
"We'll contact you via email as soon as possible."
|
||||
),
|
||||
)
|
||||
|
||||
url = reverse("products.product", args=[product.slug])
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
except APIException as err:
|
||||
messages.add_message(
|
||||
request, messages.ERROR, _("That didn't work. Please try again.")
|
||||
)
|
||||
capture_exception(err)
|
||||
|
||||
return render(request, template, context)
|
||||
|
||||
form = NewQuestionForm(
|
||||
product=product_config,
|
||||
data=request.POST or None,
|
||||
|
|
|
@ -610,8 +610,8 @@ else:
|
|||
FXA_USERNAME_ALGO = config("FXA_USERNAME_ALGO", default=_username_algo)
|
||||
FXA_STORE_ACCESS_TOKEN = config("FXA_STORE_ACCESS_TOKEN", default=False, cast=bool)
|
||||
FXA_STORE_ID_TOKEN = config("FXA_STORE_ID_TOKEN", default=False, cast=bool)
|
||||
FXA_SUPPORT_FORM = config(
|
||||
"FXA_SUPPORT_FORM", default="https://accounts.firefox.com/support"
|
||||
FXA_SUBSCRIPTIONS = config(
|
||||
"FXA_SUBSCRIPTIONS", default="https://accounts.firefox.com/subscriptions"
|
||||
)
|
||||
FXA_SET_ISSUER = config("FXA_SET_ISSUER", default="https://accounts.firefox.com")
|
||||
|
||||
|
@ -1200,3 +1200,8 @@ if ES7_ENABLE_CONSOLE_LOGGING and DEV:
|
|||
es_trace_logger.setLevel(logging.INFO)
|
||||
handler = logging.StreamHandler()
|
||||
es_trace_logger.addHandler(handler)
|
||||
|
||||
# Zendesk Section
|
||||
ZENDESK_SUBDOMAIN = config("ZENDESK_SUBDOMAIN", default="")
|
||||
ZENDESK_API_TOKEN = config("ZENDESK_API_TOKEN", default="")
|
||||
ZENDESK_USER_EMAIL = config("ZENDESK_USER_EMAIL", default="")
|
||||
|
|
|
@ -61,6 +61,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.subscriptions {
|
||||
margin: p.$spacing-lg 0;
|
||||
min-height: initial;
|
||||
}
|
||||
|
||||
&.is-shaded {
|
||||
background: var(--color-shade-bg);
|
||||
}
|
||||
|
@ -101,6 +106,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
&--subscriptions {
|
||||
padding-top: p.$spacing-sm;
|
||||
}
|
||||
|
||||
&.is-inverse {
|
||||
background: var(--color-inverse-bg);
|
||||
|
||||
|
@ -161,6 +170,7 @@
|
|||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&--product {
|
||||
|
|
|
@ -34,6 +34,11 @@
|
|||
|
||||
.aaq-widget {
|
||||
background-color: var(--card-bg);
|
||||
|
||||
.card--icon-sm {
|
||||
vertical-align: middle;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
aside .aaq-widget {
|
||||
|
|
|
@ -1,11 +1,22 @@
|
|||
from django import forms
|
||||
from django.contrib import admin
|
||||
from django.db.models import Q
|
||||
|
||||
from kitsune.products.models import Product
|
||||
from kitsune.users import monkeypatch
|
||||
from kitsune.users.models import AccountEvent, Profile
|
||||
|
||||
|
||||
class ProfileAdminForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# limit the subscriptions choices to the appropriate products
|
||||
associated_products = [product.id for product in self.initial.get("products", [])]
|
||||
products = Product.objects.filter(Q(pk__in=associated_products) | ~Q(codename__exact=""))
|
||||
|
||||
self.fields["products"].queryset = products
|
||||
self.fields["products"].required = False
|
||||
|
||||
delete_avatar = forms.BooleanField(
|
||||
required=False, help_text=("Check to remove the user's avatar.")
|
||||
)
|
||||
|
@ -30,6 +41,7 @@ class ProfileAdmin(admin.ModelAdmin):
|
|||
"is_fxa_migrated",
|
||||
"fxa_uid",
|
||||
"fxa_refresh_token",
|
||||
"zendesk_id",
|
||||
],
|
||||
},
|
||||
),
|
||||
|
@ -53,6 +65,13 @@ class ProfileAdmin(admin.ModelAdmin):
|
|||
"classes": ["collapse"],
|
||||
},
|
||||
),
|
||||
(
|
||||
"Subscriptions",
|
||||
{
|
||||
"fields": ["products"],
|
||||
"classes": ["collapse"],
|
||||
},
|
||||
),
|
||||
)
|
||||
form = ProfileAdminForm
|
||||
list_display = ["full_user", "name", "get_products"]
|
||||
|
@ -60,7 +79,7 @@ class ProfileAdmin(admin.ModelAdmin):
|
|||
list_filter = ["is_fxa_migrated", "country"]
|
||||
search_fields = ["user__username", "user__email", "name", "fxa_uid"]
|
||||
autocomplete_fields = ["user"]
|
||||
readonly_fields = ["fxa_refresh_token"]
|
||||
readonly_fields = ["fxa_refresh_token", "zendesk_id"]
|
||||
|
||||
def get_products(self, obj):
|
||||
"""Get a list of products that a user is subscribed to."""
|
||||
|
|
|
@ -11,6 +11,7 @@ from django.utils.translation import activate
|
|||
from django.utils.translation import ugettext as _
|
||||
from mozilla_django_oidc.auth import OIDCAuthenticationBackend
|
||||
|
||||
from kitsune.customercare.tasks import update_zendesk_identity
|
||||
from kitsune.products.models import Product
|
||||
from kitsune.sumo.urlresolvers import reverse
|
||||
from kitsune.users.models import Profile
|
||||
|
@ -210,6 +211,13 @@ class FXAAuthBackend(OIDCAuthenticationBackend):
|
|||
if user_attr_changed:
|
||||
user.save()
|
||||
profile.save()
|
||||
|
||||
# If we have an updated email, let's update Zendesk too
|
||||
# the check is repeated for now but it will save a few
|
||||
# API calls if we trigger the task only when we know that we have new emails
|
||||
if user_attr_changed:
|
||||
update_zendesk_identity.delay(user.id, email)
|
||||
|
||||
return user
|
||||
|
||||
def authenticate(self, request, **kwargs):
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
<li class="sidebar-nav--item"><a {{ selected|class_selected('edit-settings') }} href="{{ url('users.edit_settings') }}">{{ _('Edit settings') }}</a></li>
|
||||
<li class="sidebar-nav--item"><a {{ selected|class_selected('edit-watches') }} href="{{ url('users.edit_watch_list') }}">{{ _('Manage watch list') }}</a></li>
|
||||
<li class="sidebar-nav--item"><a {{ selected|class_selected('user-questions') }} href="{{ url('users.questions', user.username) }}">{{ _('My questions') }}</a></li>
|
||||
{% if user.profile.is_subscriber %}
|
||||
<li class="sidebar-nav--item"><a {{ selected|class_selected('user-subscriptions') }} href="{{ url('users.subscriptions', user.username) }}">{{ _('My subscriptions') }}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{%- endmacro %}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
{% extends "users/base.html" %}
|
||||
{% set title = _("{user} | Subscriptions")|f(user=display_name(user)) %}
|
||||
{% set canonical_url = canonicalize(viewname="users.subscriptions", username=user.username) %}
|
||||
{% set active = "user-subscriptions" %}
|
||||
|
||||
{% block content %}
|
||||
<article id="profile">
|
||||
<h2 class="sumo-page-subheading">
|
||||
{{ _("My Subscriptions") }}
|
||||
</h2>
|
||||
{% for product in products %}
|
||||
<div class="card card--product subscriptions">
|
||||
<img class="card--icon" src="{{ product.image_alternate_url }}" alt="{{ pgettext('DB: products.Product.title', product.title) }}">
|
||||
<div class="card--details">
|
||||
<h3 class="card--title">
|
||||
<a class="title" href="{{ url('products.product', product.slug) }}" data-event-category="link click" data-event-action="product" data-event-label="Firefox">
|
||||
{{ _('<strong>{product}</strong>')|fe(product=pgettext('DB: products.Product.title', product.title)) }}
|
||||
</a>
|
||||
</h3>
|
||||
<div class="card--subscriptions">
|
||||
<a href="{{ settings.FXA_SUBSCRIPTIONS }}">{{ _('Manage Subscriptions') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</article>
|
||||
{% endblock %}
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.24 on 2021-09-02 06:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0026_profile_fxa_refresh_token'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='profile',
|
||||
name='zendesk_id',
|
||||
field=models.CharField(blank=True, default='', max_length=1024),
|
||||
),
|
||||
]
|
|
@ -102,6 +102,7 @@ class Profile(ModelBase):
|
|||
products = models.ManyToManyField(Product, related_name="subscribed_users")
|
||||
fxa_password_change = models.DateTimeField(blank=True, null=True)
|
||||
fxa_refresh_token = models.CharField(blank=True, default="", max_length=128)
|
||||
zendesk_id = models.CharField(blank=True, default="", max_length=1024)
|
||||
|
||||
updated_column_name = "user__date_joined"
|
||||
|
||||
|
@ -197,6 +198,10 @@ class Profile(ModelBase):
|
|||
|
||||
return AnswerVote.objects.filter(answer__creator=self.user, helpful=True).count()
|
||||
|
||||
@property
|
||||
def is_subscriber(self):
|
||||
return self.products.exists()
|
||||
|
||||
|
||||
class Setting(ModelBase):
|
||||
"""User specific value per setting"""
|
||||
|
|
|
@ -18,8 +18,7 @@ detail_patterns = [
|
|||
url(r"^/answers$", views.answers_contributed, name="users.answers"),
|
||||
url(r"^/documents$", views.documents_contributed, name="users.documents"),
|
||||
url(r"^/edit$", views.edit_profile, name="users.edit_profile"),
|
||||
# TODO:
|
||||
# url('^abuse', views.report_abuse, name='users.abuse'),
|
||||
url(r"^/subscriptions$", views.subscribed_products, name="users.subscriptions"),
|
||||
]
|
||||
|
||||
if settings.DEV and settings.ENABLE_DEV_LOGIN:
|
||||
|
|
|
@ -41,14 +41,14 @@ from kitsune.questions.utils import mark_content_as_spam, num_answers, num_quest
|
|||
from kitsune.sumo.decorators import ssl_required
|
||||
from kitsune.sumo.templatetags.jinja_helpers import urlparams
|
||||
from kitsune.sumo.urlresolvers import reverse
|
||||
from kitsune.sumo.utils import get_next_url, simple_paginate, paginate
|
||||
from kitsune.sumo.utils import get_next_url, paginate, simple_paginate
|
||||
from kitsune.users.forms import ProfileForm, SettingsForm, UserForm
|
||||
from kitsune.users.models import SET_ID_PREFIX, AccountEvent, Deactivation, Profile
|
||||
from kitsune.users.tasks import (
|
||||
process_event_delete_user,
|
||||
process_event_password_change,
|
||||
process_event_subscription_state_change,
|
||||
process_event_profile_change,
|
||||
process_event_subscription_state_change,
|
||||
)
|
||||
from kitsune.users.templatetags.jinja_helpers import profile_url
|
||||
from kitsune.users.utils import (
|
||||
|
@ -214,6 +214,19 @@ def answers_contributed(request, username):
|
|||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_GET
|
||||
def subscribed_products(request, username):
|
||||
# plus sign (+) is converted to space
|
||||
username = username.replace(" ", "+")
|
||||
user = get_object_or_404(User, username=username, is_active=True)
|
||||
if user != request.user:
|
||||
raise Http404
|
||||
|
||||
context = {"user": user, "products": user.profile.products.all()}
|
||||
return render(request, "users/user_subscriptions.html", context)
|
||||
|
||||
|
||||
@require_GET
|
||||
def documents_contributed(request, username):
|
||||
# plus sign (+) is converted to space
|
||||
|
|
|
@ -72,3 +72,4 @@ timeout-decorator
|
|||
translate-toolkit~=2.5.1
|
||||
twython~=3.8.0
|
||||
whitenoise~=3.3.1
|
||||
zenpy
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# SHA1:a18c4b32b036077466b07df74766ba227261a40d
|
||||
# SHA1:0168ed530b4f334d1334aab4f5023a67f02609a6
|
||||
#
|
||||
# This file is autogenerated by pip-compile-multi
|
||||
# To update, run:
|
||||
|
@ -31,15 +31,15 @@ bleach==3.3.1 \
|
|||
# via
|
||||
# -r requirements/default.in
|
||||
# py-wikimarkup
|
||||
boto3==1.18.16 \
|
||||
--hash=sha256:23e55b7cde2b35c79c63d4d52c761fdc2141f70f02df76f68882776a33dfcb63 \
|
||||
--hash=sha256:806111acfb70715dfbe9d9f0d6089e7a9661a6d6bb422b17e035fc32e17f3f37
|
||||
boto3==1.18.41 \
|
||||
--hash=sha256:44f73009506dba227e0d421e4fc44a863d8ff315aaa47d9a7be6c549a6a88a12 \
|
||||
--hash=sha256:aaa6ba286d92fb03f27dd619220c6c1de2c010f39cac7afa72f505f073a31db1
|
||||
# via
|
||||
# -r requirements/default.in
|
||||
# django-storages
|
||||
botocore==1.21.16 \
|
||||
--hash=sha256:697b577d62a8893bce56c74ee53e54f04e69b14e42a6591e109c49b5675c19ed \
|
||||
--hash=sha256:b0e342b8c554f34f9f1cb028fbc20aff535fefe0c86a4e2cae7201846cd6aa4a
|
||||
botocore==1.21.41 \
|
||||
--hash=sha256:b877f9175843939db6fde3864ffc47611863710b85dc0336bb2433e921dc8790 \
|
||||
--hash=sha256:efad68a52ee2d939618e0fcb3da0a46dff10cb2e0e128c1e2749bbfc58953a12
|
||||
# via
|
||||
# boto3
|
||||
# s3transfer
|
||||
|
@ -49,6 +49,7 @@ cachetools==4.2.2 \
|
|||
# via
|
||||
# google-auth
|
||||
# premailer
|
||||
# zenpy
|
||||
celery==4.4.3 \
|
||||
--hash=sha256:5147662e23dc6bc39c17a2cbc9a148debe08ecfb128b0eded14a0d9c81fc5742 \
|
||||
--hash=sha256:df2937b7536a2a9b18024776a3a46fd281721813636c03a5177fa02fe66078f6
|
||||
|
@ -121,21 +122,24 @@ commonware==0.6.0 \
|
|||
--hash=sha256:0e9520986e292f2bf8cdf80b32f21ef01e4058fd7baa61d2d282d21ed7085b1f \
|
||||
--hash=sha256:f596962fd11bc53b5453ffa766dc99f297895021946096d3e6b4826f9ae075ea
|
||||
# via -r requirements/default.in
|
||||
cryptography==3.4.7 \
|
||||
--hash=sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d \
|
||||
--hash=sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959 \
|
||||
--hash=sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6 \
|
||||
--hash=sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873 \
|
||||
--hash=sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2 \
|
||||
--hash=sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713 \
|
||||
--hash=sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1 \
|
||||
--hash=sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177 \
|
||||
--hash=sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250 \
|
||||
--hash=sha256:b01fd6f2737816cb1e08ed4807ae194404790eac7ad030b34f2ce72b332f5586 \
|
||||
--hash=sha256:bf40af59ca2465b24e54f671b2de2c59257ddc4f7e5706dbd6930e26823668d3 \
|
||||
--hash=sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca \
|
||||
--hash=sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d \
|
||||
--hash=sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9
|
||||
cryptography==3.4.8 \
|
||||
--hash=sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e \
|
||||
--hash=sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b \
|
||||
--hash=sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7 \
|
||||
--hash=sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085 \
|
||||
--hash=sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc \
|
||||
--hash=sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a \
|
||||
--hash=sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498 \
|
||||
--hash=sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9 \
|
||||
--hash=sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c \
|
||||
--hash=sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7 \
|
||||
--hash=sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb \
|
||||
--hash=sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14 \
|
||||
--hash=sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af \
|
||||
--hash=sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e \
|
||||
--hash=sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5 \
|
||||
--hash=sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06 \
|
||||
--hash=sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7
|
||||
# via
|
||||
# -r requirements/default.in
|
||||
# josepy
|
||||
|
@ -314,17 +318,17 @@ faker==4.1.8 \
|
|||
# via
|
||||
# -r requirements/default.in
|
||||
# factory-boy
|
||||
google-api-core==1.31.1 \
|
||||
--hash=sha256:108cf94336aed7e614eafc53933ef02adf63b9f0fd87e8f8212acaa09eaca456 \
|
||||
--hash=sha256:1d63e2b28057d79d64795c9a70abcecb5b7e96da732d011abf09606a39b48701
|
||||
google-api-core==1.31.2 \
|
||||
--hash=sha256:384459a0dc98c1c8cd90b28dc5800b8705e0275a673a7144a513ae80fc77950b \
|
||||
--hash=sha256:8500aded318fdb235130bf183c726a05a9cb7c4b09c266bd5119b86cdb8a4d10
|
||||
# via google-api-python-client
|
||||
google-api-python-client==1.8.4 \
|
||||
--hash=sha256:bbe212611fdc05364f3d20271cae53971bf4d485056e6c0d40748eddeeda9a19 \
|
||||
--hash=sha256:e7980ba66288f815b41f10c4561b37f45cd568d302b0d801709e51f75b21f61b
|
||||
# via -r requirements/default.in
|
||||
google-auth==1.34.0 \
|
||||
--hash=sha256:bd6aa5916970a823e76ffb3d5c3ad3f0bedafca0a7fa53bc15149ab21cb71e05 \
|
||||
--hash=sha256:f1094088bae046fb06f3d1a3d7df14717e8d959e9105b79c57725bd4e17597a2
|
||||
google-auth==1.35.0 \
|
||||
--hash=sha256:997516b42ecb5b63e8d80f5632c1a61dddf41d2a4c2748057837e06e00014258 \
|
||||
--hash=sha256:b7033be9028c188ee30200b204ea00ed82ea1162e8ac1df4aa6ded19a191d88e
|
||||
# via
|
||||
# google-api-core
|
||||
# google-api-python-client
|
||||
|
@ -391,9 +395,9 @@ jmespath==0.10.0 \
|
|||
# via
|
||||
# boto3
|
||||
# botocore
|
||||
josepy==1.8.0 \
|
||||
--hash=sha256:6d632fcdaf0bed09e33f81f13b10575d4f0b7c37319350b725454e04a41e6a49 \
|
||||
--hash=sha256:a5a182eb499665d99e7ec54bb3fe389f9cbc483d429c9651f20384ba29564269
|
||||
josepy==1.9.0 \
|
||||
--hash=sha256:49798be66a467e7c81f071fe5ff03ac5e37c6d7081933612259028496bb05a68 \
|
||||
--hash=sha256:51cce8d97ced0556aae0ce3161b26d5f0f54bc42c749d3c606edc6d97d9802dc
|
||||
# via mozilla-django-oidc
|
||||
kombu==4.6.9 \
|
||||
--hash=sha256:ab0afaa5388dd2979cbc439d3623b86a4f7a58d41f621096bef7767c37bc2505 \
|
||||
|
@ -421,6 +425,7 @@ lxml==4.6.3 \
|
|||
--hash=sha256:542d454665a3e277f76954418124d67516c5f88e51a900365ed54a9806122b83 \
|
||||
--hash=sha256:5a0a14e264069c03e46f926be0d8919f4105c1623d620e7ec0e612a2e9bf1c04 \
|
||||
--hash=sha256:5c8c163396cc0df3fd151b927e74f6e4acd67160d6c33304e805b84293351d16 \
|
||||
--hash=sha256:64812391546a18896adaa86c77c59a4998f33c24788cadc35789e55b727a37f4 \
|
||||
--hash=sha256:66e575c62792c3f9ca47cb8b6fab9e35bab91360c783d1606f758761810c9791 \
|
||||
--hash=sha256:6f12e1427285008fd32a6025e38e977d44d6382cf28e7201ed10d6c1698d2a9a \
|
||||
--hash=sha256:74f7d8d439b18fa4c385f3f5dfd11144bb87c1da034a466c5b5577d23a1d9b51 \
|
||||
|
@ -435,6 +440,7 @@ lxml==4.6.3 \
|
|||
--hash=sha256:b007cbb845b28db4fb8b6a5cdcbf65bacb16a8bd328b53cbc0698688a68e1caa \
|
||||
--hash=sha256:bc4313cbeb0e7a416a488d72f9680fffffc645f8a838bd2193809881c67dd106 \
|
||||
--hash=sha256:bccbfc27563652de7dc9bdc595cb25e90b59c5f8e23e806ed0fd623755b6565d \
|
||||
--hash=sha256:c1a40c06fd5ba37ad39caa0b3144eb3772e813b5fb5b084198a985431c2f1e8d \
|
||||
--hash=sha256:c47ff7e0a36d4efac9fd692cfa33fbd0636674c102e9e8d9b26e1b93a94e7617 \
|
||||
--hash=sha256:c4f05c5a7c49d2fb70223d0d5bcfbe474cf928310ac9fa6a7c6dddc831d0b1d4 \
|
||||
--hash=sha256:cdaf11d2bd275bf391b5308f86731e5194a21af45fbaaaf1d9e8147b9160ea92 \
|
||||
|
@ -458,30 +464,50 @@ markupsafe==2.0.1 \
|
|||
--hash=sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b \
|
||||
--hash=sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567 \
|
||||
--hash=sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff \
|
||||
--hash=sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724 \
|
||||
--hash=sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74 \
|
||||
--hash=sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646 \
|
||||
--hash=sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35 \
|
||||
--hash=sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6 \
|
||||
--hash=sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6 \
|
||||
--hash=sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad \
|
||||
--hash=sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26 \
|
||||
--hash=sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38 \
|
||||
--hash=sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac \
|
||||
--hash=sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7 \
|
||||
--hash=sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6 \
|
||||
--hash=sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75 \
|
||||
--hash=sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f \
|
||||
--hash=sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135 \
|
||||
--hash=sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8 \
|
||||
--hash=sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a \
|
||||
--hash=sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a \
|
||||
--hash=sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9 \
|
||||
--hash=sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864 \
|
||||
--hash=sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914 \
|
||||
--hash=sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18 \
|
||||
--hash=sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8 \
|
||||
--hash=sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2 \
|
||||
--hash=sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d \
|
||||
--hash=sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b \
|
||||
--hash=sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b \
|
||||
--hash=sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f \
|
||||
--hash=sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb \
|
||||
--hash=sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833 \
|
||||
--hash=sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28 \
|
||||
--hash=sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415 \
|
||||
--hash=sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902 \
|
||||
--hash=sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d \
|
||||
--hash=sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9 \
|
||||
--hash=sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d \
|
||||
--hash=sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145 \
|
||||
--hash=sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066 \
|
||||
--hash=sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c \
|
||||
--hash=sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1 \
|
||||
--hash=sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f \
|
||||
--hash=sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53 \
|
||||
--hash=sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134 \
|
||||
--hash=sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85 \
|
||||
--hash=sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5 \
|
||||
--hash=sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94 \
|
||||
--hash=sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509 \
|
||||
|
@ -662,6 +688,7 @@ python-dateutil==2.8.2 \
|
|||
# botocore
|
||||
# elasticsearch-dsl
|
||||
# faker
|
||||
# zenpy
|
||||
python-decouple==3.4 \
|
||||
--hash=sha256:2e5adb0263a4f963b58d7407c4760a2465d464ee212d733e2a2c179e54c08d8f \
|
||||
--hash=sha256:a8268466e6389a639a20deab9d880faee186eb1eb6a05e54375bdf158d691981
|
||||
|
@ -681,7 +708,7 @@ pytz==2020.5 \
|
|||
# django
|
||||
# django-timezone-field
|
||||
# google-api-core
|
||||
# tzlocal
|
||||
# zenpy
|
||||
redis==3.5.3 \
|
||||
--hash=sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2 \
|
||||
--hash=sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24
|
||||
|
@ -699,6 +726,7 @@ requests==2.23.0 \
|
|||
# premailer
|
||||
# requests-oauthlib
|
||||
# twython
|
||||
# zenpy
|
||||
requests-oauthlib==1.3.0 \
|
||||
--hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \
|
||||
--hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a
|
||||
|
@ -717,45 +745,53 @@ sentry-sdk==0.14.4 \
|
|||
--hash=sha256:0e5e947d0f7a969314aa23669a94a9712be5a688ff069ff7b9fc36c66adc160c \
|
||||
--hash=sha256:799a8bf76b012e3030a881be00e97bc0b922ce35dde699c6537122b751d80e2c
|
||||
# via -r requirements/default.in
|
||||
simplejson==3.17.3 \
|
||||
--hash=sha256:02bc0b7b643fa255048862f580bb4b7121b88b456bc64dabf9bf11df116b05d7 \
|
||||
--hash=sha256:02c04b89b0a456a97d5313357dd9f2259c163a82c5307e39e7d35bb38d7fd085 \
|
||||
--hash=sha256:05cd392c1c9b284bda91cf9d7b6f3f46631da459e8546fe823622e42cf4794bb \
|
||||
--hash=sha256:1331a54fda3c957b9136402943cf8ebcd29c0c92101ba70fa8c2fc9cdf1b8476 \
|
||||
--hash=sha256:18302970ce341c3626433d4ffbdac19c7cca3d6e2d54b12778bcb8095f695473 \
|
||||
--hash=sha256:1ebbaa48447b60a68043f58e612021e8893ebcf1662a1b18a2595ca262776d7e \
|
||||
--hash=sha256:2104475a0263ff2a3dffca214c9676eb261e90d06d604ac7063347bd289ac84c \
|
||||
--hash=sha256:23169d78f74fd25f891e89c779a63fcb857e66ab210096f4069a5b1c9e2dc732 \
|
||||
--hash=sha256:32edf4e491fe174c54bf6682d794daf398736158d1082dbcae526e4a5af6890b \
|
||||
--hash=sha256:3904b528e3dc0facab73a4406ebf17f007f32f0a8d7f4c6aa9ed5cbad3ea0f34 \
|
||||
--hash=sha256:391a8206e698557a4155354cf6996c002aa447a21c5c50fb94a0d26fd6cca586 \
|
||||
--hash=sha256:3c80b343503da8b13fa7d48d1a2395be67e97b67a849eb79d88ad3b12783e7da \
|
||||
--hash=sha256:3dddd31857d8230aee88c24f485ebca36d1d875404b2ef11ac15fa3c8a01dc34 \
|
||||
--hash=sha256:56f57c231cdd01b6a1c0532ea9088dff2afe7f4f4bda61c060bcb1a853e6b564 \
|
||||
--hash=sha256:5b080be7de4c647fa84252cf565298a13842658123bd1a322a8c32b6359c8f1e \
|
||||
--hash=sha256:6285b91cfa37e024f372b9b77d14f279380eebc4f709db70c593c069602e1926 \
|
||||
--hash=sha256:6510e886d9e9006213de2090c55f504b12f915178a2056b94840ed1d89abe68e \
|
||||
--hash=sha256:6ff6710b824947ef5a360a5a5ae9809c32cedc6110df3b64f01080c1bc1a1f08 \
|
||||
--hash=sha256:79545a6d93bb38f86a00fbc6129cb091a86bb858e7d53b1aaa10d927d3b6732e \
|
||||
--hash=sha256:88a69c7e8059a4fd7aa2a31d2b3d89077eaae72eb741f18a32cb57d04018ff4c \
|
||||
--hash=sha256:8f174567c53413383b8b7ec2fbe88d41e924577bc854051f265d4c210cd72999 \
|
||||
--hash=sha256:a52b80b9d1085db6e216980d1d28a8f090b8f2203a8c71b4ea13441bd7a2e86e \
|
||||
--hash=sha256:b25748e71c5df3c67b5bda2cdece373762d319cb5f773f14ae2f90dfb4320314 \
|
||||
--hash=sha256:b45b5f6c9962953250534217b18002261c5b9383349b95fb0140899cdac2bf95 \
|
||||
--hash=sha256:b4ed7b233e812ef1244a29fb0dfd3e149dbc34a2bd13b174a84c92d0cb580277 \
|
||||
--hash=sha256:b60f48f780130f27f8d9751599925c3b78cf045f5d62dd918003effb65b45bda \
|
||||
--hash=sha256:c69a213ae72b75e8948f06a87d3675855bccb3037671222ffd235095e62f5a61 \
|
||||
--hash=sha256:c91d0f2fc2ee1bd376f5a991c24923f12416d8c31a9b74a82c4b38b942fc2640 \
|
||||
--hash=sha256:d61fb151be068127a0ce7758341cbe778495819622bc1e15eadf59fdb3a0481e \
|
||||
--hash=sha256:da72a452bcf4349fc467a12b54ab0e63e654a571cacc44084826d52bde12b6ee \
|
||||
--hash=sha256:dbcd6cd1a9abb5a13c5df93cdc5687f6877efcfefdc9350c22d4094dc4a7dd86 \
|
||||
--hash=sha256:e056056718246c9cdd82d1e3d4ad854a7ceb057498bf994b529750a190a6bd98 \
|
||||
--hash=sha256:e3aa10cce4053f3c1487aaf847a0faa4ae208e11f85a8e6f98de2291713a6616 \
|
||||
--hash=sha256:e7433c604077a17dd71e8b29c96a15e486a70a97f4ed9c7f5e0df6e428af2f0b \
|
||||
--hash=sha256:f02db159e0afa9cb350f15f4f7b86755eae95267b9012ee90bde329aa643f76c \
|
||||
--hash=sha256:f32a703fe10cfc2d1020e296eeeeb650faa039678f6b79d9b820413a4c015ddc \
|
||||
--hash=sha256:fed5e862d9b501c5673c163c8593ebdb2c5422386089c529dfac28d70cd55858 \
|
||||
--hash=sha256:ff7fe042169dd6fce8213c173a4c337f2e807ed5178093143c778eb0484c12ec
|
||||
simplejson==3.17.5 \
|
||||
--hash=sha256:065230b9659ac38c8021fa512802562d122afb0cf8d4b89e257014dcddb5730a \
|
||||
--hash=sha256:07707ba69324eaf58f0c6f59d289acc3e0ed9ec528dae5b0d4219c0d6da27dc5 \
|
||||
--hash=sha256:10defa88dd10a0a4763f16c1b5504e96ae6dc68953cfe5fc572b4a8fcaf9409b \
|
||||
--hash=sha256:140eb58809f24d843736edb8080b220417e22c82ac07a3dfa473f57e78216b5f \
|
||||
--hash=sha256:188f2c78a8ac1eb7a70a4b2b7b9ad11f52181044957bf981fb3e399c719e30ee \
|
||||
--hash=sha256:1c2688365743b0f190392e674af5e313ebe9d621813d15f9332e874b7c1f2d04 \
|
||||
--hash=sha256:24e413bd845bd17d4d72063d64e053898543fb7abc81afeae13e5c43cef9c171 \
|
||||
--hash=sha256:2b59acd09b02da97728d0bae8ff48876d7efcbbb08e569c55e2d0c2e018324f5 \
|
||||
--hash=sha256:2df15814529a4625ea6f7b354a083609b3944c269b954ece0d0e7455872e1b2a \
|
||||
--hash=sha256:352c11582aa1e49a2f0f7f7d8fd5ec5311da890d1354287e83c63ab6af857cf5 \
|
||||
--hash=sha256:36b08b886027eac67e7a0e822e3a5bf419429efad7612e69501669d6252a21f2 \
|
||||
--hash=sha256:376023f51edaf7290332dacfb055bc00ce864cb013c0338d0dea48731f37e42f \
|
||||
--hash=sha256:3ba82f8b421886f4a2311c43fb98faaf36c581976192349fef2a89ed0fcdbdef \
|
||||
--hash=sha256:3d72aa9e73134dacd049a2d6f9bd219f7be9c004d03d52395831611d66cedb71 \
|
||||
--hash=sha256:40ece8fa730d1a947bff792bcc7824bd02d3ce6105432798e9a04a360c8c07b0 \
|
||||
--hash=sha256:417b7e119d66085dc45bdd563dcb2c575ee10a3b1c492dd3502a029448d4be1c \
|
||||
--hash=sha256:42b7c7264229860fe879be961877f7466d9f7173bd6427b3ba98144a031d49fb \
|
||||
--hash=sha256:457d9cfe7ece1571770381edccdad7fc255b12cd7b5b813219441146d4f47595 \
|
||||
--hash=sha256:4a6943816e10028eeed512ea03be52b54ea83108b408d1049b999f58a760089b \
|
||||
--hash=sha256:5b94df70bd34a3b946c0eb272022fb0f8a9eb27cad76e7f313fedbee2ebe4317 \
|
||||
--hash=sha256:5f5051a13e7d53430a990604b532c9124253c5f348857e2d5106d45fc8533860 \
|
||||
--hash=sha256:5f7f53b1edd4b23fb112b89208377480c0bcee45d43a03ffacf30f3290e0ed85 \
|
||||
--hash=sha256:5fe8c6dcb9e6f7066bdc07d3c410a2fca78c0d0b4e0e72510ffd20a60a20eb8e \
|
||||
--hash=sha256:71a54815ec0212b0cba23adc1b2a731bdd2df7b9e4432718b2ed20e8aaf7f01a \
|
||||
--hash=sha256:7332f7b06d42153255f7bfeb10266141c08d48cc1a022a35473c95238ff2aebc \
|
||||
--hash=sha256:78c6f0ed72b440ebe1892d273c1e5f91e55e6861bea611d3b904e673152a7a4c \
|
||||
--hash=sha256:7c9b30a2524ae6983b708f12741a31fbc2fb8d6fecd0b6c8584a62fd59f59e09 \
|
||||
--hash=sha256:86fcffc06f1125cb443e2bed812805739d64ceb78597ac3c1b2d439471a09717 \
|
||||
--hash=sha256:87572213965fd8a4fb7a97f837221e01d8fddcfb558363c671b8aa93477fb6a2 \
|
||||
--hash=sha256:8e595de17178dd3bbeb2c5b8ea97536341c63b7278639cb8ee2681a84c0ef037 \
|
||||
--hash=sha256:917f01db71d5e720b731effa3ff4a2c702a1b6dacad9bcdc580d86a018dfc3ca \
|
||||
--hash=sha256:91cfb43fb91ff6d1e4258be04eee84b51a4ef40a28d899679b9ea2556322fb50 \
|
||||
--hash=sha256:aa86cfdeb118795875855589934013e32895715ec2d9e8eb7a59be3e7e07a7e1 \
|
||||
--hash=sha256:ade09aa3c284d11f39640aebdcbb748e1996f0c60504f8c4a0c5a9fec821e67a \
|
||||
--hash=sha256:b2a5688606dffbe95e1347a05b77eb90489fe337edde888e23bbb7fd81b0d93b \
|
||||
--hash=sha256:b92fbc2bc549c5045c8233d954f3260ccf99e0f3ec9edfd2372b74b350917752 \
|
||||
--hash=sha256:c2d5334d935af711f6d6dfeec2d34e071cdf73ec0df8e8bd35ac435b26d8da97 \
|
||||
--hash=sha256:cb0afc3bad49eb89a579103616574a54b523856d20fc539a4f7a513a0a8ba4b2 \
|
||||
--hash=sha256:ce66f730031b9b3683b2fc6ad4160a18db86557c004c3d490a29bf8d450d7ab9 \
|
||||
--hash=sha256:e29b9cea4216ec130df85d8c36efb9985fda1c9039e4706fb30e0fb6a67602ff \
|
||||
--hash=sha256:e2cc4b68e59319e3de778325e34fbff487bfdb2225530e89995402989898d681 \
|
||||
--hash=sha256:e90d2e219c3dce1500dda95f5b893c293c4d53c4e330c968afbd4e7a90ff4a5b \
|
||||
--hash=sha256:f13c48cc4363829bdfecc0c181b6ddf28008931de54908a492dc8ccd0066cd60 \
|
||||
--hash=sha256:f550730d18edec4ff9d4252784b62adfe885d4542946b6d5a54c8a6521b56afd \
|
||||
--hash=sha256:fa843ee0d34c7193f5a816e79df8142faff851549cab31e84b526f04878ac778 \
|
||||
--hash=sha256:fe1c33f78d2060719d52ea9459d97d7ae3a5b707ec02548575c4fbed1d1d345b
|
||||
# via -r requirements/default.in
|
||||
six==1.16.0 \
|
||||
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
|
||||
|
@ -770,7 +806,6 @@ six==1.16.0 \
|
|||
# elasticsearch-dsl
|
||||
# google-api-core
|
||||
# google-api-python-client
|
||||
# google-auth
|
||||
# google-auth-httplib2
|
||||
# html5lib
|
||||
# oauth2client
|
||||
|
@ -778,9 +813,10 @@ six==1.16.0 \
|
|||
# python-dateutil
|
||||
# python-memcached
|
||||
# translate-toolkit
|
||||
sqlparse==0.4.1 \
|
||||
--hash=sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0 \
|
||||
--hash=sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8
|
||||
# zenpy
|
||||
sqlparse==0.4.2 \
|
||||
--hash=sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae \
|
||||
--hash=sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d
|
||||
# via django
|
||||
text-unidecode==1.3 \
|
||||
--hash=sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8 \
|
||||
|
@ -796,17 +832,17 @@ twython==3.8.2 \
|
|||
--hash=sha256:a469d673fdd20d1c346e9b9f784212db521aa611bbdfc4912229ab701b36002b \
|
||||
--hash=sha256:c6ca64309260e0ab47267f76217c80812f591991437f376fc61498816384f9e7
|
||||
# via -r requirements/default.in
|
||||
tzlocal==2.1 \
|
||||
--hash=sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44 \
|
||||
--hash=sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4
|
||||
tzlocal==3.0 \
|
||||
--hash=sha256:c736f2540713deb5938d789ca7c3fc25391e9a20803f05b60ec64987cf086559 \
|
||||
--hash=sha256:f4e6e36db50499e0d92f79b67361041f048e2609d166e93456b50746dc4aef12
|
||||
# via apscheduler
|
||||
ua-parser==0.10.0 \
|
||||
--hash=sha256:46ab2e383c01dbd2ab284991b87d624a26a08f72da4d7d413f5bfab8b9036f8a \
|
||||
--hash=sha256:47b1782ed130d890018d983fac37c2a80799d9e0b9c532e734c67cf70f185033
|
||||
# via user-agents
|
||||
unidecode==1.2.0 \
|
||||
--hash=sha256:12435ef2fc4cdfd9cf1035a1db7e98b6b047fe591892e81f34e94959591fad00 \
|
||||
--hash=sha256:8d73a97d387a956922344f6b74243c2c6771594659778744b2dbdaad8f6b727d
|
||||
unidecode==1.3.1 \
|
||||
--hash=sha256:5f58926b9125b499f8ab6816828e737578fa3e31fa24d351a3ab7f4b7c064ab0 \
|
||||
--hash=sha256:6efac090bf8f29970afc90caf4daae87b172709b786cb1b4da2d0c0624431ecc
|
||||
# via py-wikimarkup
|
||||
uritemplate==3.0.1 \
|
||||
--hash=sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f \
|
||||
|
@ -844,6 +880,9 @@ whitenoise==3.3.1 \
|
|||
--hash=sha256:15f43b2e701821b95c9016cf469d29e2a546cb1c7dead584ba82c36f843995cf \
|
||||
--hash=sha256:9d81515f2b5b27051910996e1e860b1332e354d9e7bcf30c98f21dcb6713e0dd
|
||||
# via -r requirements/default.in
|
||||
zenpy==2.0.24 \
|
||||
--hash=sha256:4fb01f8e7f5a9bcf33a61546804953ec9dc07625c9801b30f13b5fad9370429a
|
||||
# via -r requirements/default.in
|
||||
|
||||
# WARNING: The following packages were not pinned, but pip requires them to be
|
||||
# pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag.
|
||||
|
|
Загрузка…
Ссылка в новой задаче