зеркало из https://github.com/mozilla/kitsune.git
Initial mkdocs scaffolding
This commit is contained in:
Родитель
e50e001ccb
Коммит
f9be3e6639
|
@ -0,0 +1,199 @@
|
|||
---
|
||||
title: API
|
||||
---
|
||||
|
||||
SUMO has a series of API endpoints to access data.
|
||||
|
||||
::: contents
|
||||
:::
|
||||
|
||||
# Search suggest API
|
||||
|
||||
Endpoint
|
||||
|
||||
: `/api/2/search/suggest/`
|
||||
|
||||
Method
|
||||
|
||||
: `GET`
|
||||
|
||||
Content type
|
||||
|
||||
: `application/json`
|
||||
|
||||
Response
|
||||
|
||||
: `application/json`
|
||||
|
||||
The search suggest API allows you to get back kb documents and aaq
|
||||
questions that match specified arguments.
|
||||
|
||||
Arguments can be specified in the url querystring or in the HTTP request
|
||||
body.
|
||||
|
||||
## Required arguments
|
||||
|
||||
------------------------------------------------------------------------
|
||||
argument type notes
|
||||
---------------- -------- ----------------------------------------------
|
||||
q string This is the text you\'re querying for.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
## Optional arguments
|
||||
|
||||
+---------------+------+----------------------------------------------+
|
||||
| argument | type | notes |
|
||||
+===============+======+==============================================+
|
||||
| locale | st | default: `settings.WIKI_DEFAULT_LANGUAGE` |
|
||||
| | ring | |
|
||||
| | | The locale code to restrict results to. |
|
||||
| | | |
|
||||
| | | Examples: |
|
||||
| | | |
|
||||
| | | - `en-US` |
|
||||
| | | - `fr` |
|
||||
+---------------+------+----------------------------------------------+
|
||||
| product | st | default: None |
|
||||
| | ring | |
|
||||
| | | The product to restrict results to. |
|
||||
| | | |
|
||||
| | | Example: |
|
||||
| | | |
|
||||
| | | - `firefox` |
|
||||
+---------------+------+----------------------------------------------+
|
||||
| max_documents | int | default: 10 |
|
||||
| | eger | |
|
||||
| | | The maximum number of documents you want |
|
||||
| | | back. |
|
||||
+---------------+------+----------------------------------------------+
|
||||
| max_questions | int | default: 10 |
|
||||
| | eger | |
|
||||
| | | The maximum number of questions you want |
|
||||
| | | back. |
|
||||
+---------------+------+----------------------------------------------+
|
||||
|
||||
## Responses
|
||||
|
||||
All response bodies are in JSON.
|
||||
|
||||
### HTTP 200: Success
|
||||
|
||||
With an HTTP 200, you\'ll get back a set of results in JSON.
|
||||
|
||||
{
|
||||
"documents": [
|
||||
{
|
||||
"id": ... # id of kb article
|
||||
"title": ... # title of kb article
|
||||
"url": ... # url of kb article
|
||||
"slug": ... # slug of kb article
|
||||
"locale": ... # locale of the article
|
||||
"products": ... # list of products for the article
|
||||
"topics": ... # list of topics for the article
|
||||
"summary": ... # paragraph summary of kb article (plaintext)
|
||||
"html": ... # html of the article
|
||||
}
|
||||
...
|
||||
],
|
||||
"questions": [
|
||||
{
|
||||
"id": ... # integer id of the question
|
||||
"answers": ... # list of answer ids
|
||||
"content": ... # content of question (in html)
|
||||
"created": ... # datetime stamp in iso-8601 format
|
||||
"creator": ... # JSON object describing the creator
|
||||
"involved": ... # list of JSON objects describing everyone who
|
||||
participated in the question
|
||||
"is_archived": ... # boolean for whether this question is archived
|
||||
"is_locked": ... # boolean for whether this question is locked
|
||||
"is_solved": ... # boolean for whether this question is solved
|
||||
"is_spam": ... # boolean for whether this question is spam
|
||||
"is_taken": ... # FIXME:
|
||||
"last_answer": ... # id for the last answer
|
||||
"num_answers": ... # total number of answers
|
||||
"locale": ... # the locale for the question
|
||||
"metadata": ... # metadata collected for the question
|
||||
"tags": ... # tags for the question
|
||||
"num_votes_past_week": ... # the number of votes in the last week
|
||||
"num_votes": ... # the total number of votes
|
||||
"product": ... # the product
|
||||
"solution": ... # id of answer marked as a solution if any
|
||||
"taken_until": ... # FIXME:
|
||||
"taken_by": ... # FIXME:
|
||||
"title": ... # title of the question
|
||||
"topic": ... # FIXME:
|
||||
"updated_by": ... # FIXME:
|
||||
"updated": ... # FIXME:
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
|
||||
## Examples
|
||||
|
||||
Using curl:
|
||||
|
||||
curl -X GET "http://localhost:8000/api/2/search/suggest/?q=videos"
|
||||
|
||||
curl -X GET "http://localhost:8000/api/2/search/suggest/?q=videos&max_documents=3&max_questions=3"
|
||||
|
||||
curl -X GET "http://localhost:8000/api/2/search/suggest/" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '
|
||||
{
|
||||
"q": "videos",
|
||||
"max_documents": 3,
|
||||
"max_questions": 0
|
||||
}'
|
||||
|
||||
# Locales API
|
||||
|
||||
> All locales supported by SUMO.
|
||||
>
|
||||
> **Example request**:
|
||||
>
|
||||
> ``` http
|
||||
> GET /api/2/locales/ HTTP/1.1
|
||||
> Accept: application/json
|
||||
> ```
|
||||
>
|
||||
> **Example response**:
|
||||
>
|
||||
> ``` http
|
||||
> HTTP/1.0 200 OK
|
||||
> Vary: Accept, X-Mobile, User-Agent
|
||||
> Allow: OPTIONS, GET
|
||||
> X-Frame-Options: DENY
|
||||
> Content-Type: application/json
|
||||
>
|
||||
> {
|
||||
> "vi": {
|
||||
> "name": "Vietnamese",
|
||||
> "localized_name": "Ti\u1ebfng Vi\u1ec7t",
|
||||
> "aaq_enabled": false
|
||||
> },
|
||||
> "el": {
|
||||
> "name": "Greek",
|
||||
> "localized_name": "\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac",
|
||||
> "aaq_enabled": false
|
||||
> },
|
||||
> "en-US": {
|
||||
> "name": "English",
|
||||
> "localized_name": "English",
|
||||
> "aaq_enabled": true
|
||||
> }
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> reqheader Accept
|
||||
>
|
||||
> : application/json
|
||||
>
|
||||
> resheader Content-Type
|
||||
>
|
||||
> : application/json
|
||||
>
|
||||
> statuscode 200
|
||||
>
|
||||
> : no error
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: Architectural Decision Records
|
||||
---
|
||||
|
||||
We record major architectural decisions for Kitsune/SUMO in Architecture
|
||||
Decision Records (ADR), as [described by Michael
|
||||
Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions).
|
||||
Below is the list of our current ADRs.
|
||||
|
||||
::: {.toctree maxdepth="1" glob=""}
|
||||
architecture/decisions/\*
|
||||
:::
|
|
@ -0,0 +1,68 @@
|
|||
---
|
||||
title: Badges
|
||||
---
|
||||
|
||||
::: warning
|
||||
::: title
|
||||
Warning
|
||||
:::
|
||||
|
||||
This section of documentation may be outdated.
|
||||
:::
|
||||
|
||||
Badges in kitsune are based off of [Django
|
||||
Badger](https://github.com/mozilla/django-badger),
|
||||
|
||||
As of Q3 2018, kitsune issues four badges per calendar year:
|
||||
|
||||
1. KB Badge
|
||||
2. L10n Badge
|
||||
3. Support Forum Badge
|
||||
4. Army of Awesome Badge
|
||||
|
||||
A list of active badges can be seen at
|
||||
[https://support.mozilla.org/badges/](https://support.mozilla.org/en-US/badges/).
|
||||
|
||||
# KB Badge & L10n Badge
|
||||
|
||||
The KB Badge is awarded after a user has reached 10 approved English
|
||||
edits on knowledge base articles.
|
||||
|
||||
The L10n Badge is awarded after a user has reached 10 approved
|
||||
translation edits on knowledge base articles.
|
||||
|
||||
Logic for both badges can be found in `kitsune.wiki.badges`.
|
||||
|
||||
The number of edits needed is configurable in
|
||||
`settings.BADGE_LIMIT_L10N_KB`.
|
||||
|
||||
# Support Forum Badge
|
||||
|
||||
The Support Forum Badge is awarded after a user has reached 30 support
|
||||
forum replies.
|
||||
|
||||
Logic for awarding this badge can be found in
|
||||
`kitsune.questions.badges`.
|
||||
|
||||
The number of replies needed is configurable in
|
||||
`settings.BADGE_LIMIT_SUPPORT_FORUM`.
|
||||
|
||||
# Army of Awesome Badge
|
||||
|
||||
::: warning
|
||||
::: title
|
||||
Warning
|
||||
:::
|
||||
|
||||
This badge is no longer available.
|
||||
:::
|
||||
|
||||
The Army of Awesome Badge is awarded when a user has tweeted 50 Army of
|
||||
Awesome replies.
|
||||
|
||||
## Badge Creation
|
||||
|
||||
Badges are either created manually through the Django Admin *or* created
|
||||
automatically via `get_or_create_badge` in `kitsune.kbadge.utils`.
|
||||
|
||||
Creation through the Django Admin is the usual and preferred method.
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
title: Celery
|
||||
---
|
||||
|
||||
Kitsune uses [Celery](http://celeryproject.org/) to enable offline task
|
||||
processing for long-running jobs like sending email notifications and
|
||||
re-rendering the Knowledge Base.
|
||||
|
||||
Though Celery supports multiple message backends, we use
|
||||
[Redis](https://redis.io/).
|
||||
|
||||
# When is Celery Appropriate
|
||||
|
||||
You can use Celery to do any processing that doesn\'t need to happen in
|
||||
the current request-response cycle. Examples are generating thumbnails,
|
||||
sending out notification emails, updating content that isn\'t about to
|
||||
be displayed to the user, and others.
|
||||
|
||||
Ask yourself the question: \"Is the user going to need this data on the
|
||||
page I\'m about to send them?\" If not, using a Celery task may be a
|
||||
good choice.
|
||||
|
||||
# Configuring and Running
|
||||
|
||||
Celery will automatically start when you run:
|
||||
|
||||
make run
|
||||
|
||||
We set some reasonable defaults for Celery in `settings.py`. These can
|
||||
be overriden in `.env`.
|
||||
|
||||
If you don\'t want to use Celery, you can set this in `.env`:
|
||||
|
||||
CELERY_TASK_ALWAYS_EAGER = True
|
||||
|
||||
Setting this to `True` causes all task processing to be done online.
|
||||
This is useful when debugging tasks, for instance.
|
||||
|
||||
You can also configure the concurrency. Here is the default:
|
||||
|
||||
CELERY_WORKER_CONCURRENCY = 4
|
||||
|
||||
Then to restart the Celery workers, you just need to run:
|
||||
|
||||
docker-compose restart celery
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
title: Contact us
|
||||
---
|
||||
|
||||
# SUMO contributor forums
|
||||
|
||||
If you\'re a SUMO contributor, then consider using the [contributor
|
||||
forums](https://support.mozilla.org/en-US/forums). This is the place for
|
||||
SUMO community discussions.
|
||||
|
||||
# Kitsune hackers
|
||||
|
||||
If you\'re hacking on the Kitsune code and have questions, ping us on
|
||||
[Matrix](https://wiki.mozilla.org/Matrix).
|
||||
|
||||
We hang out in
|
||||
[#support-platform:mozilla.org](https://chat.mozilla.org/#/room/#support-platform:mozilla.org).
|
||||
|
||||
If you ask something and all you get is silence, then it\'s probably the
|
||||
case that we\'re not around. Please try pinging us again.
|
||||
|
||||
Current developers:
|
||||
|
||||
- Tasos Katsoulas (tasos)
|
||||
- Ryan Johnson (ryan)
|
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
title: Join this project!
|
||||
---
|
||||
|
||||
Kitsune is the software that runs [SUMO
|
||||
(support.mozilla.org)](https://support.mozilla.org/) which provides
|
||||
support for Firefox and other Mozilla software.
|
||||
|
||||
Interested in helping out? Here\'s a bunch of things we need your help
|
||||
with.
|
||||
|
||||
# Help with support!
|
||||
|
||||
First off, you can help people get the most out of Firefox by joining
|
||||
the awesome support community. This community not only helps people with
|
||||
their Firefox issues, but is also the front line in helping drive
|
||||
Firefox development.
|
||||
|
||||
For more information on this, see the [quickstart
|
||||
guide](https://support.mozilla.org/en-US/get-involved) on the SUMO site.
|
||||
|
||||
# Help reporting bugs
|
||||
|
||||
Please report any bugs you find with Kitsune on Bugzilla:
|
||||
<https://bugzilla.mozilla.org/enter_bug.cgi?product=support.mozilla.org>
|
||||
|
||||
# Help with hacking!
|
||||
|
||||
First step is to set up Kitsune so you can run it and hack on it. For
|
||||
that, see `hacking_howto`{.interpreted-text role="any"}.
|
||||
|
||||
If you have problems, please let us know! See
|
||||
`contact-us-chapter`{.interpreted-text role="ref"}.
|
||||
|
||||
# Help with making Kitsune easier for hacking on!
|
||||
|
||||
We\'re working on making Kitsune easier to hack on. This entails:
|
||||
|
||||
- reducing the steps it takes to get Kitsune running down to a smaller
|
||||
minimal set
|
||||
- making this documentation better
|
||||
- providing better resources for people who are interested in helping
|
||||
out
|
||||
- providing better scripts to automate installing and maintaining
|
||||
Kitsune
|
||||
|
||||
Any thoughts you have on making this easier are much appreciated.
|
||||
Further, if you could help us, that\'d be valuable to us and all those
|
||||
who follow in your footsteps.
|
|
@ -0,0 +1,68 @@
|
|||
---
|
||||
title: Conventions
|
||||
---
|
||||
|
||||
This document contains coding conventions, and things to watch out for,
|
||||
etc.
|
||||
|
||||
# Coding conventions
|
||||
|
||||
We follow most of the practices as detailed in the [Mozilla webdev
|
||||
bootcamp
|
||||
guide](https://mozweb.readthedocs.io/en/latest/guide/development_process.html).
|
||||
|
||||
It is recommended that you
|
||||
`install pre-commit<hacking_howto:Install linting tools>`{.interpreted-text
|
||||
role="ref"}.
|
||||
|
||||
## Type hints
|
||||
|
||||
When creating and/or modifying Python functions/methods, we add [type
|
||||
hints](https://docs.python.org/3/library/typing.html) to their arguments
|
||||
and result, but only when it makes sense. See
|
||||
`our Architectural Decision Record<architecture/decisions/0004-type-checking>`{.interpreted-text
|
||||
role="doc"} for more details.
|
||||
|
||||
# Git conventions
|
||||
|
||||
## Git workflow
|
||||
|
||||
See `patching`{.interpreted-text role="ref"} for how we use Git,
|
||||
branches and merging.
|
||||
|
||||
## Git commit messages
|
||||
|
||||
Git commit messages should have the following form:
|
||||
|
||||
[bug xxxxxxx] Short summary
|
||||
|
||||
Longer explanation with paragraphs and lists and all that where
|
||||
each line is under 72 characters.
|
||||
|
||||
* bullet 1
|
||||
* bullet 2
|
||||
|
||||
Etc. etc.
|
||||
|
||||
Summary line should be capitalized, short and shouldn\'t exceed 50
|
||||
characters. Why? Because this is a convention many git tools take
|
||||
advantage of.
|
||||
|
||||
If the commit relates to a bug, the bug should show up in the summary
|
||||
line in brackets.
|
||||
|
||||
There should be a blank line between the summary and the rest of the
|
||||
commit message. Lines shouldn\'t exceed 72 characters.
|
||||
|
||||
See [these
|
||||
guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
||||
for some more explanation.
|
||||
|
||||
## Git resources and tools
|
||||
|
||||
See [Webdev bootcamp
|
||||
guide](https://mozweb.readthedocs.io/en/latest/reference/git_github.html)
|
||||
for:
|
||||
|
||||
- helpful resources for learning git
|
||||
- helpful tools
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
title: Kitsune Deployments
|
||||
---
|
||||
|
||||
This documents the current development (dev), staging and production
|
||||
(prod) servers for the `support.mozilla.com` instance of
|
||||
[Kitsune](https://github.com/mozilla/kitsune).
|
||||
|
||||
# The Source
|
||||
|
||||
All of the source code for Kitsune lives in [a single Github
|
||||
repo](https://github.com/mozilla/kitsune).
|
||||
|
||||
# Branches
|
||||
|
||||
## main
|
||||
|
||||
The `main` branch is our main integration points. All new patches should
|
||||
be based on the latest `main` (or rebased to it).
|
||||
|
||||
Pull requests are created from those branches. Pull requests may be
|
||||
opened at any time, including before any code has been written.
|
||||
|
||||
Pull requests get reviewed.
|
||||
|
||||
Once reviewed, the branch is merged into `main`, except in special cases
|
||||
such as changes that require re-indexing. See
|
||||
`Changes that involve reindexing <changes_reindexing>`{.interpreted-text
|
||||
role="ref"}.
|
||||
|
||||
We deploy to production from `main`.
|
||||
|
||||
# Deploying
|
||||
|
||||
We currently use Kubernetes for our infrastructure, see the instructions
|
||||
here for how to do deployments:
|
||||
|
||||
<https://github.com/mozilla/kitsune/blob/main/k8s/README.md>
|
||||
|
||||
# Servers
|
||||
|
||||
## Development
|
||||
|
||||
<https://support-dev.allizom.org/>
|
||||
|
||||
We use dev primarily to develop infrastructure changes.
|
||||
|
||||
## Staging
|
||||
|
||||
<https://support.allizom.org/>
|
||||
|
||||
We deploy to stage anything we want to test including deployments
|
||||
themselves.
|
||||
|
||||
## Production
|
||||
|
||||
<https://support.mozilla.org/>
|
|
@ -0,0 +1,231 @@
|
|||
---
|
||||
title: Development
|
||||
---
|
||||
|
||||
This covers loosely how we do big feature changes.
|
||||
|
||||
# Changes that involve new Python dependencies
|
||||
|
||||
All python dependencies have an associated hash (or several) that are
|
||||
checked at download time. This ensures malicious code doesn\'t sneak in
|
||||
through dependencies being hacked, and also makes sure we always get the
|
||||
exact code we developed against. Changes in dependencies, malicious or
|
||||
not, will set off red flags and require human intervention.
|
||||
|
||||
A pip requirement stanza with hashes looks something like this:
|
||||
|
||||
Django==1.8.15 \
|
||||
--hash=sha256:e2e41aeb4fb757575021621dc28fceb9ad137879ae0b854067f1726d9a772807 \
|
||||
--hash=sha256:863e543ac985d5cfbce09213fa30bc7c802cbdf60d6db8b5f9dab41e1341eacd
|
||||
|
||||
hash lines can be repeated, and other comments can be added. The stanza
|
||||
is delimited by non-comment lines (such as blank lines or other
|
||||
requirements).
|
||||
|
||||
Fortunately we do not need to add or edit those manaully. Using
|
||||
[pip-compile-multi](https://github.com/peterdemin/pip-compile-multi) we
|
||||
list only our top-level dependencies in
|
||||
[requirements/\*.in]{.title-ref}. To add a dependency, put it in the
|
||||
appropriate [requirements/\*.in]{.title-ref} file, then compile:
|
||||
|
||||
pip-compile-multi -g default
|
||||
|
||||
# Changes that involve database migrations
|
||||
|
||||
Any changes to the database (model fields, model field data, adding
|
||||
permissions, \...) require a migration.
|
||||
|
||||
## Running migrations
|
||||
|
||||
To run migrations, you do:
|
||||
|
||||
$ ./manage.py migrate
|
||||
|
||||
It\'ll perform any migrations that haven\'t been performed for all apps.
|
||||
|
||||
## Creating a schema migration
|
||||
|
||||
To create a new migration the automatic way:
|
||||
|
||||
1. make your model changes
|
||||
|
||||
2. run:
|
||||
|
||||
./manage.py makemigrations <app>
|
||||
|
||||
where `<app>` is the app name (sumo, wiki, questions, \...).
|
||||
|
||||
3. run the migration on your machine:
|
||||
|
||||
./manage.py migrate
|
||||
|
||||
4. run the tests to make sure everything works
|
||||
|
||||
5. add the new migration files to git
|
||||
|
||||
6. commit
|
||||
|
||||
::: seealso
|
||||
|
||||
<https://docs.djangoproject.com/en/stable/topics/migrations/#adding-migrations-to-apps>
|
||||
|
||||
: Django documentation: Adding migrations to apps
|
||||
:::
|
||||
|
||||
# Creating a data migration
|
||||
|
||||
Creating data migrations is pretty straight-forward in most cases.
|
||||
|
||||
To create a data migration the automatic way:
|
||||
|
||||
1. run:
|
||||
|
||||
./manage.py makemigrations --empty <app>
|
||||
|
||||
where `<app>` is the app name (sumo, wiki, questions, \...).
|
||||
|
||||
2. edit the data migration you just created to do what you need it to
|
||||
do
|
||||
|
||||
3. make sure to add [reverse_code]{.title-ref} arguments to all
|
||||
[RunPython]{.title-ref} operations which undoes the changes
|
||||
|
||||
4. add a module-level docstring explaining what this migration is doing
|
||||
|
||||
5. run the migration forwards and backwards to make sure it works
|
||||
correctly
|
||||
|
||||
6. add the new migration file to git
|
||||
|
||||
7. commit
|
||||
|
||||
::: seealso
|
||||
|
||||
<https://docs.djangoproject.com/en/stable/topics/migrations/#data-migrations>
|
||||
|
||||
: Django documentation: Data Migrations
|
||||
:::
|
||||
|
||||
::: seealso
|
||||
<https://docs.djangoproject.com/en/stable/ref/migration-operations/#runpython>
|
||||
:::
|
||||
|
||||
## Data migrations for data in non-kitsune apps
|
||||
|
||||
If you\'re doing a data migration that adds data to an app that\'s not
|
||||
part of kitsune, but is instead a library (e.g. django-waffle), then
|
||||
create the data migration in the sumo app and add a dependency to the
|
||||
latest migration in the library app.
|
||||
|
||||
For example, this adds a dependency to django-waffle\'s initial
|
||||
migration:
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
...
|
||||
('waffle', '0001_initial'),
|
||||
...
|
||||
]
|
||||
|
||||
# Changes that involve reindexing {#changes_reindexing}
|
||||
|
||||
With Elastic Search, it takes a while to reindex. We need to be able to
|
||||
reindex without taking down search.
|
||||
|
||||
This walks through the workflow for making changes to our Elastic Search
|
||||
code that require reindexing.
|
||||
|
||||
## Things about non-trivial changes
|
||||
|
||||
1. We should roll multiple reindex-requiring changes into megapacks
|
||||
when it makes sense and doesn\'t add complexity.
|
||||
2. Developers should test changes with recent sumo dumps.
|
||||
|
||||
## Workflow for making the changes
|
||||
|
||||
1. work on the changes in a separate branch (just like everything else
|
||||
we do)
|
||||
|
||||
2. make a pull request
|
||||
|
||||
3. get the pull request reviewed
|
||||
|
||||
4. rebase the changes so they\'re in two commits:
|
||||
|
||||
1. a stage 1 commit that changes `ES_WRITE_INDEXES`, updates the
|
||||
mappings and updates the indexing code
|
||||
2. a stage 2 commit that changes `ES_INDEXES`, changes
|
||||
`ES_WRITE_INDEXES`, and changes the search view code
|
||||
|
||||
**Avoid cosmetic changes that don\'t need to be made (e.g. pep-8
|
||||
fixes, etc.)**
|
||||
|
||||
5. push those changes to the same pull request
|
||||
|
||||
6. get those two changes reviewed
|
||||
|
||||
Once that\'s ok, then that branch should get updated from main, then
|
||||
pushed to stage to get tested.
|
||||
|
||||
That branch should **not** land in main, yet.
|
||||
|
||||
## Workflow for reviewing changes
|
||||
|
||||
Go through and do a normal review.
|
||||
|
||||
After everything looks good, the developer should rebase the changes so
|
||||
they\'re in a stage 1 commit and a stage 2 commit.
|
||||
|
||||
At that point:
|
||||
|
||||
1. Verify each commit individually. Make sure the code is correct. Make
|
||||
sure the tests pass. Make sure the site is functional.
|
||||
2. Verify that the `ES_INDEXES` and `ES_WRITE_INDEXES` settings have
|
||||
the correct values in each commit.
|
||||
|
||||
## Workflow for pushing changes to stage
|
||||
|
||||
Don\'t land the changes in main, yet!
|
||||
|
||||
If you hit problems, deploy the main branch back to the stage server and
|
||||
go back to coding/fixing.
|
||||
|
||||
1. Push the branch you have your changes in to the official
|
||||
mozilla/kitsune remote.
|
||||
2. Deploy the stage 1 commit to stage.
|
||||
3. Verify that search still works.
|
||||
4. Verify that the index settings are correct\-\--look at the
|
||||
`ES_INDEXES` and `ES_WRITE_INDEXES` values.
|
||||
5. Destructively reindex.
|
||||
6. Deploy the stage 2 commit to stage.
|
||||
7. Verify that search still works.
|
||||
8. Verify that the index settings are correct\-\--look at the
|
||||
`ES_INDEXES` and `ES_WRITE_INDEXES` values.
|
||||
9. Verify bugs that were fixed with the new search code.
|
||||
|
||||
## Workflow for pushing those changes to production
|
||||
|
||||
If we\'re also doing a production push, first push next to production
|
||||
and verify that everything is fine. Then continue.
|
||||
|
||||
1. Tell the other sumo devs to hold off on pushing to main branch and
|
||||
deploying. Preferably by email and IRC.
|
||||
2. Once you\'ve told everyone, land the changes in main.
|
||||
3. Deploy the stage 1 commit to production.
|
||||
4. Verify that search works.
|
||||
5. Destructively reindex to the new write index.
|
||||
6. When reindexing is done, push the stage 2 commit to production.
|
||||
7. Verify that search works.
|
||||
8. Verify bugs that were fixed with the new search code.
|
||||
|
||||
Pretty sure this process allows us to back out at any time with minimal
|
||||
downtime.
|
||||
|
||||
## On the next day
|
||||
|
||||
If everything is still fine, then merge the special branch into main and
|
||||
delete the old read index.
|
||||
|
||||
Announce \"STUCK THE LANDING!\" after a successful mapping change
|
||||
deployment.
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
title: Email from Kitsune
|
||||
---
|
||||
|
||||
The default settings for Kitsune *do not send email*. However, outgoing
|
||||
email is printed to the the command line.
|
||||
|
||||
# Viewing email through Mailcatcher
|
||||
|
||||
To view the contents of outgoing email in a slightly easier form than
|
||||
the command line, [Mailcatcher](https://mailcatcher.me/) can be used.
|
||||
This still won\'t send the email, but show a web-based \"outbox\" with
|
||||
the contents of all email which would be sent if Kitsune was hooked up
|
||||
to an email server.
|
||||
|
||||
The docker-compose config includes a mailcatcher container, which can be
|
||||
brought up with:
|
||||
|
||||
docker-compose up mailcatcher
|
||||
|
||||
Kitsune should then be configured to use it:
|
||||
|
||||
EMAIL_LOGGING_REAL_BACKEND = django.core.mail.backends.smtp.EmailBackend
|
||||
EMAIL_HOST = mailcatcher
|
||||
EMAIL_HOST_USER =
|
||||
EMAIL_HOST_PASSWORD =
|
||||
EMAIL_PORT = 1025
|
||||
EMAIL_USE_TLS = False
|
||||
|
||||
Now all outgoing email will be captured, and can be viewed through
|
||||
<http://localhost:1080/>.
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
title: Welcome to Kitsune\'s documentation!
|
||||
---
|
||||
|
||||
# Part 1: Contributor\'s Guide
|
||||
|
||||
::: {.toctree maxdepth="2"}
|
||||
contributors hacking_howto contactus
|
||||
:::
|
||||
|
||||
# Part 2: Developer\'s Guide
|
||||
|
||||
::: {.toctree maxdepth="2"}
|
||||
conventions patching development tests celery email localization
|
||||
elastic_search frontend svelte browser_permissions zendesk seo notes
|
||||
switching_devices
|
||||
:::
|
||||
|
||||
# Part 3: SUMO
|
||||
|
||||
::: {.toctree maxdepth="2"}
|
||||
api deployments k8s sla architectural-decisions
|
||||
:::
|
||||
|
||||
# Part 4: User Guide
|
||||
|
||||
::: {.toctree maxdepth="2"}
|
||||
users questions badges advanced-search
|
||||
:::
|
|
@ -0,0 +1,520 @@
|
|||
---
|
||||
title: Localization
|
||||
---
|
||||
|
||||
::: {.contents local=""}
|
||||
:::
|
||||
|
||||
Kitsune is localized with
|
||||
[gettext](http://www.gnu.org/software/gettext/). User-facing strings in
|
||||
the code or templates need to be marked for gettext localization.
|
||||
|
||||
We use [Pontoon](https://pontoon.mozilla.org/) to provide an easy
|
||||
interface to localizing these files. Localizers are also free to
|
||||
download the PO files and use whatever tool they are comfortable with.
|
||||
|
||||
# Making Strings Localizable
|
||||
|
||||
Making strings in templates localizable is exceptionally easy. Making
|
||||
strings in Python localizable is a little more complicated. The short
|
||||
answer, though, is just wrap the string in `_()`.
|
||||
|
||||
## Interpolation
|
||||
|
||||
A string is often a combination of a fixed string and something
|
||||
changing, for example, `Welcome, James` is a combination of the fixed
|
||||
part `Welcome,`, and the changing part `James`. The naive solution is to
|
||||
localize the first part and the follow it with the name:
|
||||
|
||||
_('Welcome, ') + username
|
||||
|
||||
This is **wrong!**
|
||||
|
||||
In some locales, the word order may be different. Use Python string
|
||||
formatting to interpolate the changing part into the string:
|
||||
|
||||
_('Welcome, {name}').format(name=username)
|
||||
|
||||
Python gives you a lot of ways to interpolate strings. The best way is
|
||||
to use Py3k formatting and kwargs. That\'s the clearest for localizers.
|
||||
|
||||
The worst way is to use `%(label)s`, as localizers seem to have all
|
||||
manner of trouble with it. Options like `%s` and `{0}` are somewhere in
|
||||
the middle, and generally OK if it\'s clear from context what they will
|
||||
be.
|
||||
|
||||
## Localization Comments
|
||||
|
||||
Sometimes, it can help localizers to describe where a string comes from,
|
||||
particularly if it can be difficult to find in the interface, or is not
|
||||
very self-descriptive (e.g. very short strings). If you immediately
|
||||
precede the string with a comment that starts `L10n:`, the comment will
|
||||
be added to the PO file, and visible to localizers.
|
||||
|
||||
Example:
|
||||
|
||||
rev_data.append({
|
||||
'x': 1000 * int(time.mktime(rdate.timetuple())),
|
||||
# L10n: 'R' is the first letter of "Revision".
|
||||
'title': _('R', 'revision_heading'),
|
||||
'text': str(_('Revision %s')) % rev.created
|
||||
#'url': 'http://www.google.com/' # Not supported yet
|
||||
})
|
||||
|
||||
## Adding Context with msgctxt
|
||||
|
||||
Strings may be the same in English, but different in other languages.
|
||||
English, for example, has no grammatical gender, and sometimes the noun
|
||||
and verb forms of a word are identical.
|
||||
|
||||
To make it possible to localize these correctly, we can add \"context\"
|
||||
(known in gettext as \"msgctxt\") to differentiate two otherwise
|
||||
identical strings.
|
||||
|
||||
For example, the string \"Search\" may be a noun or a verb in English.
|
||||
In a heading, it may be considered a noun, but on a button, it may be a
|
||||
verb. It\'s appropriate to add a context (like \"button\") to one of
|
||||
them.
|
||||
|
||||
Generally, we should only add context if we are sure the strings aren\'t
|
||||
used in the same way, or if localizers ask us to.
|
||||
|
||||
Example:
|
||||
|
||||
from tower import ugettext as _
|
||||
|
||||
...
|
||||
|
||||
foo = _('Search', context='text for the search button on the form')
|
||||
|
||||
## Plurals
|
||||
|
||||
\"You have 1 new messages\" grates on discerning ears. Fortunately,
|
||||
gettext gives us a way to fix that in English *and* other locales, the
|
||||
`ngettext` function:
|
||||
|
||||
ngettext('singular', 'plural', count)
|
||||
|
||||
A more realistic example might be:
|
||||
|
||||
ngettext('Found {count} result.',
|
||||
'Found {count} results',
|
||||
len(results)).format(count=len(results))
|
||||
|
||||
This method takes three arguments because English only needs three,
|
||||
i.e., zero is considered \"plural\" for English. Other locales may have
|
||||
different plural rules, and require different phrases for, say 0, 1,
|
||||
2-3, 4-10, \>10. That\'s absolutely fine, and gettext makes it possible.
|
||||
|
||||
## Strings in HTML Templates
|
||||
|
||||
When putting new text into a template, all you need to do is wrap it in
|
||||
a `_()` call:
|
||||
|
||||
<h1>{{ _('Heading') }}</h1>
|
||||
|
||||
Adding context is easy, too:
|
||||
|
||||
<h1>{{ _('Heading', 'context') }}</h1>
|
||||
|
||||
L10n comments need to be Jinja2 comments:
|
||||
|
||||
{# L10n: Describes this heading #}
|
||||
<h1>{{ _('Heading') }}</h1>
|
||||
|
||||
Note that Jinja2 escapes all content output through `{{ }}` by default.
|
||||
To put HTML in a string, you\'ll need to add the `|safe` filter:
|
||||
|
||||
<h1>{{ _('Firefox <span>Help</span>')|safe }}</h1>
|
||||
|
||||
To interpolate, you should use one of two Jinja2 filters: `|f()` or, in
|
||||
some cases, `|fe()`. `|f()` has exactly the same arguments as
|
||||
`u''.format()`:
|
||||
|
||||
{{ _('Welcome, {name}!')|f(name=request.user.username) }}
|
||||
|
||||
The `|fe()` is exactly like the `|f()` filter, but escapes its arguments
|
||||
before interpolating, then returns a \"safe\" object. Use it when the
|
||||
localized string contains HTML:
|
||||
|
||||
{{ _('Found <strong>{0}</strong> results.')|fe(num_results) }}
|
||||
|
||||
Note that you *do not need* to use `|safe` with `|fe()`. Also note that
|
||||
while it may look similar, the following is *not* safe:
|
||||
|
||||
{{ _('Found <strong>{0}</strong> results.')|f(num_results)|safe }}
|
||||
|
||||
The `ngettext` function is also available:
|
||||
|
||||
{{ ngettext('Found {0} result.',
|
||||
'Found {0} results.',
|
||||
num_results)|f(num_results) }}
|
||||
|
||||
### Using `{% trans %}` Blocks for Long Strings
|
||||
|
||||
When a string is very long, i.e. long enough to make Github scroll
|
||||
sideways, it should be line-broken and put in a `{% trans %}` block.
|
||||
`{% trans %}` blocks work like other block-level tags in Jinja2, except
|
||||
they cannot have other tags, except strings, inside them.
|
||||
|
||||
The only thing that should be inside a `{% trans %}` block is printing a
|
||||
string with `{{ string }}`. These are defined in the opening
|
||||
`{% trans %}` tag:
|
||||
|
||||
{% trans user=request.user.username %}
|
||||
Thanks for registering, {{ user }}! We're so...
|
||||
hope that you'll...
|
||||
{% endtrans %}
|
||||
|
||||
You can also provide comments:
|
||||
|
||||
{# L10n: User is a username #}
|
||||
{% trans user=request.user.username %}
|
||||
Thanks for registering, {{ user }}! We're so...
|
||||
hope that you'll...
|
||||
{% endtrans %}
|
||||
|
||||
When a block contains HTML with attributes, those which don\'t need to
|
||||
be localized should be passed as arguments. This ensures strings won\'t
|
||||
need to be re-localized if those attributes change:
|
||||
|
||||
{% trans url="http://example.com" %}
|
||||
Please visit <a href="{{ url }}" title="External Site">our FAQ</a> for more information.
|
||||
{% endtrans %}
|
||||
|
||||
## Strings in Python
|
||||
|
||||
::: note
|
||||
::: title
|
||||
Note
|
||||
:::
|
||||
|
||||
Whenever you are adding a string in Python, ask yourself if it really
|
||||
needs to be there, or if it should be in the template. Keep logic and
|
||||
presentation separate!
|
||||
:::
|
||||
|
||||
Strings in Python are more complex for two reasons:
|
||||
|
||||
1. We need to make sure we\'re always using Unicode strings and the
|
||||
Unicode-friendly versions of the functions.
|
||||
2. If you use the `ugettext` function in the wrong place, the string
|
||||
may end up in the wrong locale!
|
||||
|
||||
Here\'s how you might localize a string in a view:
|
||||
|
||||
from tower import ugettext as _
|
||||
|
||||
def my_view(request):
|
||||
if request.user.is_superuser:
|
||||
msg = _(u'Oh hi, staff!')
|
||||
else:
|
||||
msg = _(u'You are not staff!')
|
||||
|
||||
Interpolation is done through normal Python string formatting:
|
||||
|
||||
msg = _(u'Oh, hi, {user}').format(user=request.user.username)
|
||||
|
||||
`ugettext` supports context, too:
|
||||
|
||||
msg = _('Search', 'context')
|
||||
|
||||
L10n comments are normal one-line Python comments:
|
||||
|
||||
# L10n: A message to users.
|
||||
msg = _(u'Oh, hi there!')
|
||||
|
||||
If you need to use plurals, import the function `ungettext` from Tower:
|
||||
|
||||
from tower import ungettext, ugettext as _
|
||||
|
||||
n = len(results)
|
||||
msg = ungettext('Found {0} result', 'Found {0} results', n).format(n)
|
||||
|
||||
### Lazily Translated Strings
|
||||
|
||||
You can use `ugettext` or `ungettext` only in views or functions called
|
||||
from views. If the function will be evaluated when the module is loaded,
|
||||
then the string may end up in English or the locale of the last request!
|
||||
(We\'re tracking down that issue.)
|
||||
|
||||
Examples include strings in module-level code, arguments to functions in
|
||||
class definitions, strings in functions called from outside the context
|
||||
of a view. To localize these strings, you need to use the `_lazy`
|
||||
versions of the above methods, `ugettext_lazy` and `ungettext_lazy`. The
|
||||
result doesn\'t get translated until it is evaluated as a string, for
|
||||
example by being output or passed to `str()`:
|
||||
|
||||
from tower import ugettext_lazy as _lazy
|
||||
|
||||
PAGE_TITLE = _lazy(u'Page Title')
|
||||
|
||||
`ugettext_lazy` also supports context.
|
||||
|
||||
It is very important to pass Unicode objects to the `_lazy` versions of
|
||||
these functions. Failure to do so results in significant issues when
|
||||
they are evaluated as strings.
|
||||
|
||||
If you need to work with a lazily-translated string, you\'ll first need
|
||||
to convert it to a `str` object:
|
||||
|
||||
from tower import ugettext_lazy as _lazy
|
||||
|
||||
WELCOME = _lazy(u'Welcome, %s')
|
||||
|
||||
def my_view(request):
|
||||
# Fails:
|
||||
WELCOME % request.user.username
|
||||
|
||||
# Works:
|
||||
str(WELCOME) % request.user.username
|
||||
|
||||
## Strings in the Database
|
||||
|
||||
There is some user generated content that needs to be localizable. For
|
||||
example, karma titles can be created in the admin site and need to be
|
||||
localized when displayed to users. A django management command is used
|
||||
for this. The first step to making a model\'s field localizable is
|
||||
adding it to `DB_LOCALIZE` in `settings.py`:
|
||||
|
||||
``` python
|
||||
DB_LOCALIZE = {
|
||||
'karma': {
|
||||
'Title': {
|
||||
'attrs': ['name'],
|
||||
'comments': ['This is a karma title.'],
|
||||
}
|
||||
},
|
||||
'appname': {
|
||||
'ModelName': {
|
||||
'attrs': ['field_name'],
|
||||
'comments': ['Optional comments for localizers.'],
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, all you need to do is run the `extract_db` management command:
|
||||
|
||||
$ python manage.py extract_db
|
||||
|
||||
*Be sure to have a recent database from production when running the
|
||||
command.*
|
||||
|
||||
By default, this will write all the strings to
|
||||
[kitsune/sumo/db_strings.py]{.title-ref} and they will get picked up
|
||||
during the normal string extraction (see below).
|
||||
|
||||
## Strings in Email Templates
|
||||
|
||||
Currently, email templates are text-based and not in HTML. Because of
|
||||
that you should use this style guide:
|
||||
|
||||
1. The entire email should be wrapped in autoescape. e.g.
|
||||
|
||||
``` {.html+jinja linenos=""}
|
||||
{% autoescape false %}
|
||||
{% trans %}
|
||||
The entire email should be wrapped in autoescape.
|
||||
{% endtrans %}
|
||||
|
||||
|
||||
...
|
||||
{% endautoescape %}
|
||||
```
|
||||
|
||||
2. After an `{% endtrans %}`, you need two blank lines (three carriage
|
||||
returns). The first is eaten by the tag. The other two show up in
|
||||
the email. e.g.
|
||||
|
||||
``` {.jinja linenos=""}
|
||||
{% trans %}
|
||||
To confirm your subscription, stand up, put your hands on
|
||||
your hips and do the hokey pokey.
|
||||
{% endtrans %}
|
||||
|
||||
|
||||
{{ _('Thanks!') }}
|
||||
```
|
||||
|
||||
Produces this:
|
||||
|
||||
``` {.text linenos=""}
|
||||
To confirm your subscription, stand up, put your hands on
|
||||
your hips and do the hokey pokey.
|
||||
|
||||
Thanks!
|
||||
```
|
||||
|
||||
3. Putting in line breaks in a `trans` block doesn\'t have an effect
|
||||
since `trans` blocks get gettexted and whitespace is collapsed.
|
||||
|
||||
# Testing localized strings
|
||||
|
||||
When we add strings that need to be localized, it can take a couple of
|
||||
weeks for us to get translations of those localized strings. This makes
|
||||
it difficult to find localization issues.
|
||||
|
||||
Enter [Dennis](https://github.com/willkg/dennis/).
|
||||
|
||||
Run:
|
||||
|
||||
$ ./scripts/test_locales.sh
|
||||
|
||||
It\'ll extract all the strings, create a `.pot` file, then create a
|
||||
Pirate translation of all strings. The Pirate strings are available in
|
||||
the xx locale. After running the `test_locales.sh` script, you can
|
||||
access the xx locale with:
|
||||
|
||||
> <http://localhost:8000/xx/>
|
||||
|
||||
Strings in the Pirate translation have the following properties:
|
||||
|
||||
1. they are longer than the English string: helps us find layout and
|
||||
wrapping issues
|
||||
2. they have at least one unicode character: helps us find unicode
|
||||
issues
|
||||
3. they are easily discernable from the English versions: helps us find
|
||||
strings that aren\'t translated
|
||||
|
||||
::: note
|
||||
::: title
|
||||
Note
|
||||
:::
|
||||
|
||||
The xx locale is only available on your local machine. It is not
|
||||
available on -dev, -stage, or -prod.
|
||||
:::
|
||||
|
||||
# Linting localized strings
|
||||
|
||||
You can lint localized strings for warnings and errors:
|
||||
|
||||
$ dennis-cmd lint locale/
|
||||
|
||||
Or just errors:
|
||||
|
||||
$ dennis-cmd lint --errorsonly locale/
|
||||
|
||||
You can see help text:
|
||||
|
||||
$ dennis-cmd
|
||||
|
||||
# Getting the Localizations {#getting-localizations}
|
||||
|
||||
Localizations are not stored in this repository, but are in a separate
|
||||
Git repo:
|
||||
|
||||
> <https://github.com/mozilla-l10n/sumo-l10n>
|
||||
|
||||
You don\'t need the localization files for general development. However,
|
||||
if you need them for something, they\'re pretty easy to get:
|
||||
|
||||
$ cd kitsune
|
||||
$ git clone https://github.com/mozilla-l10n/sumo-l10n locale
|
||||
|
||||
# Updating the Localizations
|
||||
|
||||
When strings are added or updated, we need to update the templates and
|
||||
PO files for localizers. Updating strings is pretty easy. Check out the
|
||||
localizations as above, then:
|
||||
|
||||
$ python manage.py extract
|
||||
$ python manage.py merge
|
||||
|
||||
Congratulations! You\'ve now updated the POT and PO files.
|
||||
|
||||
Sometimes this can leave a bunch of garbage files with `.po~`
|
||||
extensions. You should delete these, never commit them:
|
||||
|
||||
$ find . -name "*.po~" -delete
|
||||
|
||||
## Adding a New Locale
|
||||
|
||||
Say you wanted to add `fa-IR`:
|
||||
|
||||
$ mkdir -p locale/fa-IR/LC_MESSAGES
|
||||
$ python manage.py merge
|
||||
|
||||
Then add \'fa-IR\' to SUMO_LANGUAGES in settings.py and make sure there
|
||||
is an entry in lib/languages.json (if not, add it).
|
||||
|
||||
And finally, add a migration with:
|
||||
|
||||
INSERT INTO `wiki_locale` (`locale`) VALUES ('fa-IR');
|
||||
|
||||
Done!
|
||||
|
||||
# Compiling MO Files
|
||||
|
||||
gettext is so fast for localization because it doesn\'t parse text
|
||||
files, it reads a binary format. You can easily compile that binary file
|
||||
from the PO files in the repository.
|
||||
|
||||
We don\'t store MO files in the repository because they need to change
|
||||
every time the corresponding PO file changes, so it\'s silly and not
|
||||
worth it. They are ignored by `.gitignore`, but please make sure you
|
||||
don\'t forcibly add them to the repository.
|
||||
|
||||
There is a shell script to compile the MO files for you:
|
||||
|
||||
$ ./locale/compile-mo.sh locale
|
||||
|
||||
Done!
|
||||
|
||||
# Why aren\'t localized strings getting updated on prod?
|
||||
|
||||
We use Dennis to
|
||||
`lint .po files for errors<localization:Linting localized strings>`{.interpreted-text
|
||||
role="ref"} that cause HTTP 500 errors in production. Things like
|
||||
malformed variables, variables in the translated string that aren\'t in
|
||||
the original and that sort of thing.
|
||||
|
||||
For example, this would cause the site to break:
|
||||
|
||||
#: kitsune/questions/templates/questions/includes/answer.html:19
|
||||
msgid "{num} answers"
|
||||
msgstr "{0} antwoorden"
|
||||
|
||||
In this example, the `{0}` is wrong.
|
||||
|
||||
## Reporting errors in .po files
|
||||
|
||||
When we do a deployment to production, we dump all the Dennis output
|
||||
into:
|
||||
|
||||
<https://support.mozilla.org/static/postatus.txt>
|
||||
|
||||
We need to check that periodically and report the errors.
|
||||
|
||||
If there are errors in those files, we need to open up a bug in
|
||||
**Mozilla Localizations** -\> *locale code* with the specifics.
|
||||
|
||||
Product:
|
||||
|
||||
> Mozilla Localizations
|
||||
|
||||
Component:
|
||||
|
||||
> The locale code for the language in question
|
||||
|
||||
Bug summary:
|
||||
|
||||
> Use the error line
|
||||
|
||||
Bug description template:
|
||||
|
||||
> We found errors in the translated strings for Mozilla Support
|
||||
> <https://support.mozilla.org/>. The errors are as follows:
|
||||
>
|
||||
>
|
||||
> <paste errors here>
|
||||
>
|
||||
>
|
||||
> Until these errors are fixed, we can't deploy updates to the
|
||||
> strings for this locale to production.
|
||||
>
|
||||
> Mozilla Support strings can be fixed in the Support Mozilla project
|
||||
> in Pontoon <https://pontoon.mozilla.org/projects/sumo/>.
|
||||
>
|
||||
> If you have any questions, let us know.
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
title: Other Notes
|
||||
---
|
||||
|
||||
::: warning
|
||||
::: title
|
||||
Warning
|
||||
:::
|
||||
|
||||
This section of documentation may be outdated.
|
||||
:::
|
||||
|
||||
# Questions
|
||||
|
||||
## memcached
|
||||
|
||||
::: note
|
||||
::: title
|
||||
Note
|
||||
:::
|
||||
|
||||
This should probably be somewhere else, but the easy way to flush your
|
||||
cache is something like this:
|
||||
|
||||
echo "flush_all" | nc localhost 11211
|
||||
|
||||
Assuming you have memcache configured to listen to 11211.
|
||||
:::
|
|
@ -0,0 +1,294 @@
|
|||
---
|
||||
title: Patching Kitsune
|
||||
---
|
||||
|
||||
::: warning
|
||||
::: title
|
||||
Warning
|
||||
:::
|
||||
|
||||
This section of documentation may be outdated.
|
||||
:::
|
||||
|
||||
Submitting a patch to [Kitsune](https://support.mozilla.com) is easy!
|
||||
(Fair warning: writing the patch may not be ;)
|
||||
|
||||
We use [pull requests](https://github.com/mozilla/kitsune/pulls) to
|
||||
manage patches and code reviews, and
|
||||
[Bugzilla](https://bugzilla.mozilla.org) to handle actual bug tracking.
|
||||
|
||||
Because of our infrastructure and how we do deployments, we\'ve
|
||||
developed a fairly straight-forward workflow in git for submitting
|
||||
patches. This is outlined below.
|
||||
|
||||
You should run the tests before submitting a pull request. You can find
|
||||
help for getting set up in the
|
||||
`installation docs <hacking_howto>`{.interpreted-text role="any"} and
|
||||
help for running tests in the
|
||||
`testing docs <tests-chapter>`{.interpreted-text role="ref"}.
|
||||
|
||||
If you ever find yourself stuck,
|
||||
`contact us <contactus>`{.interpreted-text role="any"}. We\'re happy to
|
||||
help!
|
||||
|
||||
You\'ll need a Github account and a Bugzilla account.
|
||||
|
||||
# The Quick and Dirty
|
||||
|
||||
Very quick, very little explanation. Those with strong git fu may
|
||||
already see some shortcuts. Use them!
|
||||
|
||||
First, clone your fork, and then point the main branch to Mozilla\'s
|
||||
fork. Assuming your Github account is `foobar` and you\'ve already
|
||||
forked Kitsune:
|
||||
|
||||
git clone https://github.com/foobar/kitsune
|
||||
cd kitsune
|
||||
git remote add mozilla https://github.com/mozilla/kitsune.git
|
||||
git fetch mozilla
|
||||
git checkout -t mozilla/main -B main
|
||||
|
||||
If you haven\'t set up your local git user, please do before committing
|
||||
any code for Kitsune. This way you can take credit for your work:
|
||||
|
||||
git config user.email your@github.email
|
||||
git config user.name "Your Name"
|
||||
|
||||
You should only need to do that once. Here\'s the bit to do every time:
|
||||
|
||||
git checkout main
|
||||
git reset --hard mozilla/main
|
||||
git checkout -b my-feature-123456
|
||||
|
||||
# Make a change and commit it.
|
||||
$EDITOR path/to/file.py
|
||||
git add path/to/file.py
|
||||
git commit -m "[Bug 123456] Fooing and the Barring."
|
||||
git push --set-upstream origin my-feature
|
||||
|
||||
# Open a pull request, get review.
|
||||
# Respond to feedback:
|
||||
$EDITOR path/to/file.py
|
||||
git add path/to/file.py
|
||||
git commit -m "Feedback from Barfoo"
|
||||
git push
|
||||
|
||||
Eventually you\'ll get an r+. If you have commit access, now you can go
|
||||
ahead and merge your branch. You may, if you want, rebase your branch to
|
||||
clean up any embarrassing mistakes, but it isn\'t required. If you
|
||||
don\'t have commit access the next part will be done by someone who
|
||||
does.
|
||||
|
||||
There are two options. The first is to press the Big Green Button in
|
||||
GitHub PRs that says \"Merge pull Request\". If you would prefer to do
|
||||
it manually (or if there are merge conflicts, you can do this:
|
||||
|
||||
# r+! Merge
|
||||
git checkout main
|
||||
git fetch mozilla
|
||||
git reset --hard mozilla/main
|
||||
git merge --no-ff my-feature-123456
|
||||
git push mozilla main # Bots will alert everyone!
|
||||
git push origin main # Optional but nice.
|
||||
|
||||
After the pull request is closed:
|
||||
|
||||
git push origin :my-feature # Delete the remote branch. Nice to others.
|
||||
git branch -D my-feature # Delete the local branch, if you're done.
|
||||
|
||||
# The Details
|
||||
|
||||
This is the process in more detail, for a relatively small change that
|
||||
will only need one commit, and doesn\'t need any special treatment, like
|
||||
landing on special branches.
|
||||
|
||||
## Fork and Clone Kitsune
|
||||
|
||||
On Github, hit the **Fork** button. You\'ll want to clone **your** fork
|
||||
of the project, at least initially:
|
||||
|
||||
git clone git@github.com:<yourname>/kitsune.git
|
||||
|
||||
To help keep up to date, you should add `mozilla/kitsune` as a remote:
|
||||
|
||||
cd kitsune
|
||||
git remote add mozilla https://github.com/mozilla/kitsune.git
|
||||
|
||||
You should avoid changing your `main` branch, it should track
|
||||
`mozilla/main`. This can help:
|
||||
|
||||
git fetch mozilla
|
||||
# Update your main branch to track Mozilla's main branch instead.
|
||||
git checkout -B main -t mozilla/main # Update your main branch to
|
||||
|
||||
If you haven\'t set up your local git user, please do before committing
|
||||
any code for Kitsune. This way you can take credit for your work:
|
||||
|
||||
git config user.email your@github.email
|
||||
git config user.name "Your Name"
|
||||
|
||||
The correct way to keep your local main up to date is:
|
||||
|
||||
git checkout main
|
||||
git fetch mozilla
|
||||
git reset --hard mozilla/main
|
||||
|
||||
This will forcibly move your local main branch to whatever is on the
|
||||
Mozilla main branch, destroying anything you have committed that wasn\'t
|
||||
pushed. Remember to always work on a branch that is not main!
|
||||
|
||||
## Find a Bug
|
||||
|
||||
Step one is to make sure there\'s a bug in Bugzilla. Obvious \"bugs\"
|
||||
just need a Bugzilla bug to track the work for all the involved teams.
|
||||
There are [a number of open bugs](http://bit.ly/LUTjcY) if you want to
|
||||
try your hand at fixing something!
|
||||
|
||||
New features or changes to features need bugs to build a consensus of
|
||||
developers, support team members, and community members, before we
|
||||
decide to make the change. If you want to change something like this, be
|
||||
sure to file the bug and get a consensus first. We\'d hate to have you
|
||||
spend time on a patch we can\'t take.
|
||||
|
||||
## Take the Bug
|
||||
|
||||
To make sure no one else is working on the bug at the same time, assign
|
||||
it to yourself in Bugzilla. If you have the proper permissions there\'s
|
||||
an easy \"take\" link next to the Assignee field. Ask in the IRC for
|
||||
details.
|
||||
|
||||
You can assign bugs to yourself even if you aren\'t going to immediately
|
||||
work on them (though make sure you will get to them sooner rather than
|
||||
later). Once you are actively working on a bug, set the bug to the
|
||||
`ASSIGNED` state.
|
||||
|
||||
## Fix the Bug on a Branch
|
||||
|
||||
::: note
|
||||
::: title
|
||||
Note
|
||||
:::
|
||||
|
||||
This describes the process for fixing a relatively small bug in a
|
||||
single-commit. Large features may differ.
|
||||
:::
|
||||
|
||||
All bug fixes, changes, new features, etc, should be done on a \"feature
|
||||
branch\", which just means \"any branch besides `main`.\" You should
|
||||
make sure your local `main` branch is up to date (see above) before
|
||||
starting a new feature branch. Your feature branch should include the
|
||||
bug number in the branch name, if applicable.
|
||||
|
||||
git checkout main
|
||||
git fetch mozilla
|
||||
git reset --hard upstream/main # Update local main.
|
||||
git checkout -b my-feature-branch-123456 # Some logical name.
|
||||
|
||||
Now you\'re on a feature branch, go ahead and make your changes.
|
||||
Assuming you haven\'t added any new files, you can do:
|
||||
|
||||
git commit -a -m "[Bug 123456] Fix the foo and the bar."
|
||||
|
||||
If you did add new files, you will have to `git add` them before
|
||||
committing.
|
||||
|
||||
Note that the commit message contains the bug number after the word
|
||||
\"Bug\". This helps us and our IRC bots!
|
||||
|
||||
## Open a Pull Request
|
||||
|
||||
Once you have the bug fixed locally, you\'ll need to push the changes up
|
||||
to Github so you can open a pull request.
|
||||
|
||||
git push --set-upstream origin my-feature-branch
|
||||
|
||||
Then, in your browser, navigate to
|
||||
`https://github.com/<yourname>/kitsune/compare/my-feature-branch` and
|
||||
hit the **Pull Request** button. If the commit message is clear, the
|
||||
form should be filled out enough for you to submit it right away.
|
||||
|
||||
We add an `r?` in the pull request message indicating that this pull
|
||||
request is ready to go and is looking for someone to review it.
|
||||
|
||||
Othertimes you may want to open a pull request early that isn\'t quite
|
||||
ready to merge. This is a great way to share the work that you are
|
||||
doing, and get early feedback. Make it clear that your PR isn\'t ready
|
||||
by putting `[WIP]` in the title. Also make sure to say when it is ready!
|
||||
The best way to do this is to remove `[WIP]` from the title and make a
|
||||
comment asking for `r?`.
|
||||
|
||||
## Respond to Review
|
||||
|
||||
It\'s very rare that pull requests will be checked in immediately. Most
|
||||
of the time they will go through one or more rounds of code review and
|
||||
clean-up.
|
||||
|
||||
Code review is usually comments made on the pull request or commits in
|
||||
Github, asking for specific changes to be made. If the requested change
|
||||
isn\'t clear, or you disagree with it, feel free to ask questions
|
||||
inline. Isn\'t Github\'s line-by-line commenting great?
|
||||
|
||||
Assuming a few small changes need to be made, make the changes locally
|
||||
on the feature branch, then put them in a *new commit*. This makes it
|
||||
easier from reviewers. For example, if Erik reviewed the pull request
|
||||
and asked for some fixes, you might do this:
|
||||
|
||||
git checkout my-feature-branch
|
||||
# Make the changes.
|
||||
git commit -a -m "Feedback from Erik."
|
||||
git push origin my-feature-branch
|
||||
|
||||
Github will automatically add the new commit to the pull request, so
|
||||
we\'ll see it. Leaving it in a separate commit at this stage helps the
|
||||
reviewer see what changes you\'ve made.
|
||||
|
||||
There may be more than one round of feedback, especially for complex
|
||||
bugs. The process is exactly the same after each round: make the
|
||||
changes, add them in yet another new commit, push the changes.
|
||||
|
||||
There are also a few bots that might interact with your PR. In
|
||||
particular, our continuous integration service will run tests and style
|
||||
checks on your new code. All PRs must be approved by the CI system
|
||||
before they will be merged, so watch out. They show up as either a red X
|
||||
or a green check mark in the PR.
|
||||
|
||||
## Ready to Merge!
|
||||
|
||||
Once a pull request has gotten an `r+` (\"R-plus\", it\'s from Bugzilla)
|
||||
it\'s ready to merge in. At this point you can rebase and squash any
|
||||
feedback/fixup commits you want, but this isn\'t required.
|
||||
|
||||
If you don\'t have commit access, someone who does may do this for you,
|
||||
if they have time. Alternatively, if you have commit access, you can
|
||||
press GitHub\'s \"Merge pull request\" button, which does a similar
|
||||
process to below. This is the preferred way to merge PRs when there are
|
||||
no complications.
|
||||
|
||||
git checkout main
|
||||
git reset --hard mozilla/main
|
||||
git merge --no-ff my-feature-branch-123456
|
||||
# Make sure tests pass.
|
||||
python manage.py test
|
||||
git push
|
||||
|
||||
You\'re done! Congratulations, soon you\'ll have code running on one of
|
||||
the biggest sites in the world!
|
||||
|
||||
Before pushing to `mozilla/main`, I like to verify that the merge went
|
||||
fine in the logs. For the vast majority of merges, *there should not be
|
||||
a merge commit*.
|
||||
|
||||
git log --graph --decorate
|
||||
git push mozilla main # !!! Pushing code to the primary repo/branch!
|
||||
|
||||
# Optionally, you can keep your Github main in sync.
|
||||
git push origin main # Not strictly necessary but kinda nice.
|
||||
git push origin :my-feature-branch # Nice to clean up.
|
||||
|
||||
This should automatically close the PR, as GitHub will notice the merge
|
||||
commit.
|
||||
|
||||
Once the commit is on `mozilla/main`, copy the commit url to the bug.
|
||||
|
||||
Once the commit has been deployed to stage and prod, set the bug to
|
||||
`RESOLVED FIXED`. This tells everyone that the fix is in production.
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
title: Ask A Question
|
||||
---
|
||||
|
||||
This document explains what kinds of question states exist in Kitsune,
|
||||
how they are set and their implications.
|
||||
|
||||
# Configuring new products
|
||||
|
||||
To configure a new product for AAQ you must edit `config.py` within the
|
||||
questions app.
|
||||
|
||||
First, ensure the `Product` object exists for this product in the
|
||||
products app. If not create a new `Product`.
|
||||
|
||||
Next, Add a new item to the `products` dictionary using something like:
|
||||
|
||||
('product-slug', {
|
||||
'name': _lazy(u'Product Name'),
|
||||
'subtitle': _lazy('A brief description'),
|
||||
'extra_fields': [],
|
||||
'tags': ['tag-slug'],
|
||||
'product': 'product-slug',
|
||||
'categories': SortedDict([
|
||||
('topic-slug', {
|
||||
'name': _lazy(u'Topic name'),
|
||||
'topic': 'topic-slug',
|
||||
'tags': []
|
||||
}),
|
||||
])
|
||||
}),
|
||||
|
||||
`'product-slug'` should be the slug of the `Product` object for this
|
||||
product.
|
||||
|
||||
# Question States
|
||||
|
||||
## Default
|
||||
|
||||
This is the unmarked state of the thread.
|
||||
|
||||
Implications:
|
||||
|
||||
- Users can reply
|
||||
- Visible in regular SUMO searches (with at least one helpful reply)
|
||||
- Visible to external searches
|
||||
- Visible in the regular questions list
|
||||
- Visible in the [related threads]{.title-ref} section
|
||||
|
||||
## Locked
|
||||
|
||||
This is the locked state of a thread. A thread can be locked in two
|
||||
ways:
|
||||
|
||||
- By manually locking it via the question UI
|
||||
- Automatically after 180 days.
|
||||
|
||||
Implications:
|
||||
|
||||
- Users can\'t reply
|
||||
- Moderators can unlock to reply
|
||||
- If there is an answer, the locked thread is still shown by search
|
||||
engines and our internal search.
|
||||
- If there is no answer, the locked thread will not be shown by search
|
||||
engines and our internal search.
|
||||
|
||||
## Not indexed
|
||||
|
||||
Questions with no answers that are older than 30 days have a meta tag
|
||||
telling search engines not to show them.
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
title: SEO
|
||||
---
|
||||
|
||||
This document covers notes and policies related to SEO.
|
||||
|
||||
# Prefer `meta` tag if possible
|
||||
|
||||
If an entire page should not be indexed, and/or none of its links
|
||||
followed, prefer to use the `<meta name="robots" ...>` tag by specifying
|
||||
something like:
|
||||
|
||||
{% set meta = (('robots', 'noindex'),) %}
|
||||
|
||||
or:
|
||||
|
||||
{% set meta = (('robots', 'noindex, nofollow'),) %}
|
||||
|
||||
within the lowest-level Jinja2 templates of the inheritance chain that
|
||||
apply to only the desired pages.
|
||||
|
||||
However, if you only want to discourage the crawling of specific links
|
||||
within a page, you\'ll have to add `rel="nofollow"` to each of those
|
||||
links within its template. For example:
|
||||
|
||||
<a rel="nofollow" href="...">...</a>
|
||||
|
||||
# Breadcrumbs
|
||||
|
||||
If one or more of the breadcrumb links for a page should not be crawled,
|
||||
you can add an extra string to those breadcrumb tuples to specify the
|
||||
proper attribute to use, for example:
|
||||
|
||||
{% set crumbs = [((profile_url(user), 'rel="nofollow"'), user.username), (None, title)] %}
|
||||
|
||||
or:
|
||||
|
||||
{% set crumbs = [(document.get_absolute_url(), document.title), ((url('wiki.discuss.threads', document.slug), 'rel="ugc nofollow"'), _('Discuss'))] %}
|
||||
|
||||
# KB Forums
|
||||
|
||||
KB forums are user-generated content about KB articles. They are not
|
||||
official content, and therefore not meant to be searchable. All links to
|
||||
KB forums should be marked with `rel="ugc nofollow"`.
|
||||
|
||||
# User Links
|
||||
|
||||
User-related pages are also not meant to be indexed (searchable), and
|
||||
links to them should not be crawled, so the base user template
|
||||
(`kitsune/users/jinja2/users/base.html`) contains:
|
||||
|
||||
{% set meta = (('robots', 'noindex'),) %}
|
||||
|
||||
and all user links on all pages should be marked with `rel="nofollow"`.
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
title: Service Level Agreement
|
||||
---
|
||||
|
||||
::: warning
|
||||
::: title
|
||||
Warning
|
||||
:::
|
||||
|
||||
This section of documentation may be outdated.
|
||||
:::
|
||||
|
||||
This isn\'t a zero-tolerance policy, but a series of policy points we
|
||||
work towards when making changes to the site.
|
||||
|
||||
Measurements are based on what we can see in graphite which means it\'s
|
||||
all server-side. Also, we use the upper_90 line since that tracks the
|
||||
more extreme side of performance.
|
||||
|
||||
This SLA will probably change over time. Here it is now.
|
||||
|
||||
1. View performance
|
||||
|
||||
upper_90 for server-side rendering of views for GET requests should
|
||||
be under 800ms.
|
||||
|
||||
2. Search availability
|
||||
|
||||
Search should work and return useful results.
|
||||
|
||||
The implication here is that it\'s not ok to be reindexing into an
|
||||
index that searches are against.
|
||||
|
||||
3. Browser support
|
||||
|
||||
See this page in the wiki:
|
||||
|
||||
<https://wiki.mozilla.org/Support/Browser_Support>
|
|
@ -0,0 +1,153 @@
|
|||
---
|
||||
title: All about testing
|
||||
---
|
||||
|
||||
::: warning
|
||||
::: title
|
||||
Warning
|
||||
:::
|
||||
|
||||
This section of documentation may be outdated.
|
||||
:::
|
||||
|
||||
Kitsune has a fairly comprehensive Python test suite. Changes should not
|
||||
break tests\-\--only change a test if there is a good reason to change
|
||||
the expected behavior\-\--and new code should come with tests.
|
||||
|
||||
# Running the Test Suite
|
||||
|
||||
If you followed the steps in `the installation docs
|
||||
<hacking_howto>`{.interpreted-text role="any"}, then you should be all
|
||||
set setup-wise.
|
||||
|
||||
To run the tests, you need to do:
|
||||
|
||||
./manage.py test
|
||||
|
||||
That doesn\'t provide the most sensible defaults for running the tests.
|
||||
Here is a good command to alias to something short:
|
||||
|
||||
./manage.py test -s --noinput --logging-clear-handlers
|
||||
|
||||
The `-s` flag is important if you want to be able to drop into PDB from
|
||||
within tests.
|
||||
|
||||
Some other helpful flags are:
|
||||
|
||||
`-x`:
|
||||
|
||||
: Fast fail. Exit immediately on failure. No need to run the whole
|
||||
test suite if you already know something is broken.
|
||||
|
||||
`--pdb`:
|
||||
|
||||
: Drop into PDB on an uncaught exception. (These show up as `E` or
|
||||
errors in the test results, not `F` or failures.)
|
||||
|
||||
`--pdb-fail`:
|
||||
|
||||
: Drop into PDB on a test failure. This usually drops you right at the
|
||||
assertion.
|
||||
|
||||
`--no-skip`:
|
||||
|
||||
: All SkipTests show up as errors. This is handy when things
|
||||
shouldn\'t be skipping silently with reckless abandon.
|
||||
|
||||
## Running a Subset of Tests
|
||||
|
||||
You can run part of the test suite by specifying the apps you want to
|
||||
run, like:
|
||||
|
||||
./manage.py test kitsune/wiki kitsune/search kitsune/kbforums
|
||||
|
||||
You can also specify modules:
|
||||
|
||||
./manage.py test kitsune.wiki.tests.test_views
|
||||
|
||||
You can specify specific tests:
|
||||
|
||||
./manage.py test kitsune.wiki.tests.test_views:VersionGroupTests.test_version_groups
|
||||
|
||||
See the output of `./manage.py test --help` for more arguments.
|
||||
|
||||
## Running tests without collecting static files
|
||||
|
||||
By default the test runner will run `collectstatic` to ensure that all
|
||||
the required assets have been collected to the static folder. If you do
|
||||
not want this default behavior you can run:
|
||||
|
||||
REUSE_STATIC=1 ./manage.py test
|
||||
|
||||
## The Test Database
|
||||
|
||||
The test suite will create a new database named `test_%s` where `%s` is
|
||||
whatever value you have for `settings.DATABASES['default']['NAME']`.
|
||||
|
||||
Make sure the user has `ALL` on the test database as well. This is
|
||||
covered in the installation chapter.
|
||||
|
||||
When the schema changes, you may need to drop the test database. You can
|
||||
also run the test suite with `FORCE_DB` once to cause Django to drop and
|
||||
recreate it:
|
||||
|
||||
FORCE_DB=1 ./manage.py test -s --noinput --logging-clear-handlers
|
||||
|
||||
# Writing New Tests
|
||||
|
||||
Code should be written so it can be tested, and then there should be
|
||||
tests for it.
|
||||
|
||||
When adding code to an app, tests should be added in that app that cover
|
||||
the new functionality. All apps have a `tests` module where tests should
|
||||
go. They will be discovered automatically by the test runner as long as
|
||||
the look like a test.
|
||||
|
||||
- We use \"modelmakers\" instead of fixtures. Models should have
|
||||
modelmakers defined in the tests module of the Django app. For
|
||||
example, `forums.tests.document` is the modelmaker for
|
||||
`forums.Models.Document` class.
|
||||
|
||||
# Changing Tests
|
||||
|
||||
Unless the current behavior, and thus the test that verifies that
|
||||
behavior is correct, is demonstrably wrong, don\'t change tests. Tests
|
||||
may be refactored as long as its clear that the result is the same.
|
||||
|
||||
# Removing Tests
|
||||
|
||||
On those rare, wonderful occasions when we get to remove code, we should
|
||||
remove the tests for it, as well.
|
||||
|
||||
If we liberate some functionality into a new package, the tests for that
|
||||
functionality should move to that package, too.
|
||||
|
||||
# JavaScript Tests
|
||||
|
||||
Frontend JavaScript is currently tested with
|
||||
[Mocha](https://mochajs.org/).
|
||||
|
||||
## Running JavaScript Tests
|
||||
|
||||
To run tests, make sure you have have the NPM dependencies installed,
|
||||
and then run:
|
||||
|
||||
$ npm run webpack:test
|
||||
|
||||
## Writing JavaScript Tests
|
||||
|
||||
Mocha tests are discovered using the pattern
|
||||
`kitsune/*/static/*/js/tests/**/*.js`. That means that any app can have
|
||||
a [tests]{.title-ref} directory in its JavaScript directory, and the
|
||||
files in there will all be considered test files. Files that don\'t
|
||||
define tests won\'t cause issues, so it is safe to put testing utilities
|
||||
in these directories as well.
|
||||
|
||||
Here are a few tips for writing tests:
|
||||
|
||||
- Any HTML required for your test should be added by the tests or a
|
||||
`beforeEach` function in that test suite. React is useful for this.
|
||||
- You can use [sinon]{.title-ref} to mock out parts of libraries or
|
||||
functions under test. This is useful for testing AJAX.
|
||||
- The tests run in a Node.js environment. A browser environment can be
|
||||
simulated using `jsdom`.
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
subtitle: Mozilla Accounts
|
||||
title: Users
|
||||
---
|
||||
|
||||
Kitsune uses Mozilla accounts for authentication:
|
||||
<https://support.mozilla.org/kb/firefox-accounts-mozilla-support-faq>
|
|
@ -0,0 +1,98 @@
|
|||
site_name: Kitsune Documentation
|
||||
site_url: https://support.mozilla.org
|
||||
site_description: 'Kitsune is the platform that powers SuMo'
|
||||
repo_name: 'mozilla/kitsune'
|
||||
repo_url: 'https://github.com/mozilla/kitsune'
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- abbr
|
||||
- def_list
|
||||
- tables
|
||||
- toc:
|
||||
baselevel: 2
|
||||
permalink: true
|
||||
- pymdownx.betterem:
|
||||
smart_enable: all
|
||||
- pymdownx.arithmatex:
|
||||
generic: true
|
||||
- pymdownx.caret
|
||||
- pymdownx.critic
|
||||
- pymdownx.details
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.magiclink
|
||||
- pymdownx.highlight
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.keys
|
||||
- pymdownx.mark
|
||||
- pymdownx.smartsymbols
|
||||
- pymdownx.superfences
|
||||
- pymdownx.tabbed:
|
||||
alternate_style: true
|
||||
- pymdownx.tasklist:
|
||||
custom_checkbox: true
|
||||
- pymdownx.tilde
|
||||
- pymdownx.superfences
|
||||
theme:
|
||||
name: material
|
||||
features:
|
||||
- navigation.instant
|
||||
- navigation.instant.progress
|
||||
- navigation.tracking
|
||||
- navigation.top
|
||||
- toc.follow
|
||||
palette:
|
||||
# Palette toggle for light mode
|
||||
- scheme: default
|
||||
media: "(prefers-color-scheme: light)"
|
||||
primary: deep purple
|
||||
accent: deep orange
|
||||
toggle:
|
||||
icon: material/brightness-7
|
||||
name: Switch to dark mode
|
||||
# Palette toggle for dark mode
|
||||
- scheme: slate
|
||||
media: "(prefers-color-scheme: dark)"
|
||||
primary: deep purple
|
||||
accent: deep orange
|
||||
toggle:
|
||||
icon: material/brightness-4
|
||||
name: Switch to light mode
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Introduction:
|
||||
- contributors.md
|
||||
- hacking_howto.md
|
||||
- contactus.md
|
||||
- Developer's guide:
|
||||
- conventions.md
|
||||
- patching.md
|
||||
- development.md
|
||||
- tests.md
|
||||
- celery.md
|
||||
- email.md
|
||||
- localization.md
|
||||
- elastic_search.md
|
||||
- frontend.md
|
||||
- svelte.md
|
||||
- browser_permissions.md
|
||||
- zendesk.md
|
||||
- seo.md
|
||||
- notes.md
|
||||
- switching_devices.md
|
||||
- SuMo:
|
||||
- api.md
|
||||
- deployments.md
|
||||
- k8s.md
|
||||
- sla.md
|
||||
- User Guide:
|
||||
- users.md
|
||||
- questions.md
|
||||
- badges.md
|
||||
- advanced-search.md
|
||||
- Architectural Decisions:
|
||||
- architectural-decisions.md
|
||||
- Record: architecture/decisions/0001-record-architecture-decisions.md
|
||||
- Search - L10N content: architecture/decisions/0002-es-l10n-content.md
|
||||
- Search - AAQ content: architecture/decisions/0003-es-aaq-documents.md
|
||||
- Type Checking: architecture/decisions/0004-type-checking.md
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "alabaster"
|
||||
|
@ -1487,6 +1487,23 @@ monitor = ["psutil (>=5.7.0)"]
|
|||
recommended = ["cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "psutil (>=5.7.0)"]
|
||||
test = ["cffi (>=1.12.2)", "coverage (>=5.0)", "dnspython (>=1.16.0,<2.0)", "idna", "objgraph", "psutil (>=5.7.0)", "requests", "setuptools"]
|
||||
|
||||
[[package]]
|
||||
name = "ghp-import"
|
||||
version = "2.1.0"
|
||||
description = "Copy your docs directly to the gh-pages branch."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"},
|
||||
{file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
python-dateutil = ">=2.8.1"
|
||||
|
||||
[package.extras]
|
||||
dev = ["flake8", "markdown", "twine", "wheel"]
|
||||
|
||||
[[package]]
|
||||
name = "google-api-core"
|
||||
version = "2.11.0"
|
||||
|
@ -2299,6 +2316,21 @@ html5 = ["html5lib"]
|
|||
htmlsoup = ["BeautifulSoup4"]
|
||||
source = ["Cython (>=0.29.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "markdown"
|
||||
version = "3.5.1"
|
||||
description = "Python implementation of John Gruber's Markdown."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "Markdown-3.5.1-py3-none-any.whl", hash = "sha256:5874b47d4ee3f0b14d764324d2c94c03ea66bee56f2d929da9f2508d65e722dc"},
|
||||
{file = "Markdown-3.5.1.tar.gz", hash = "sha256:b65d7beb248dc22f2e8a31fb706d93798093c308dc1aba295aedeb9d41a813bd"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
|
||||
testing = ["coverage", "pyyaml"]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.1.2"
|
||||
|
@ -2383,6 +2415,87 @@ files = [
|
|||
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mergedeep"
|
||||
version = "1.3.4"
|
||||
description = "A deep merge function for 🐍."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
|
||||
{file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs"
|
||||
version = "1.5.3"
|
||||
description = "Project documentation with Markdown."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1"},
|
||||
{file = "mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7.0"
|
||||
colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""}
|
||||
ghp-import = ">=1.0"
|
||||
jinja2 = ">=2.11.1"
|
||||
markdown = ">=3.2.1"
|
||||
markupsafe = ">=2.0.1"
|
||||
mergedeep = ">=1.3.4"
|
||||
packaging = ">=20.5"
|
||||
pathspec = ">=0.11.1"
|
||||
platformdirs = ">=2.2.0"
|
||||
pyyaml = ">=5.1"
|
||||
pyyaml-env-tag = ">=0.1"
|
||||
watchdog = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["babel (>=2.9.0)"]
|
||||
min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pathspec (==0.11.1)", "platformdirs (==2.2.0)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-material"
|
||||
version = "9.4.10"
|
||||
description = "Documentation that simply works"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mkdocs_material-9.4.10-py3-none-any.whl", hash = "sha256:207c4ebc07faebb220437d2c626edb0c9760c82ccfc484500bd3eb30dfce988c"},
|
||||
{file = "mkdocs_material-9.4.10.tar.gz", hash = "sha256:421adedaeaa461dcaf55b8d406673934ade3d4f05ed9819e4cc7b4ee1d646a62"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
babel = ">=2.10,<3.0"
|
||||
colorama = ">=0.4,<1.0"
|
||||
jinja2 = ">=3.0,<4.0"
|
||||
markdown = ">=3.2,<4.0"
|
||||
mkdocs = ">=1.5.3,<2.0"
|
||||
mkdocs-material-extensions = ">=1.3,<2.0"
|
||||
paginate = ">=0.5,<1.0"
|
||||
pygments = ">=2.16,<3.0"
|
||||
pymdown-extensions = ">=10.2,<11.0"
|
||||
regex = ">=2022.4"
|
||||
requests = ">=2.26,<3.0"
|
||||
|
||||
[package.extras]
|
||||
git = ["mkdocs-git-committers-plugin-2 (>=1.1,<2.0)", "mkdocs-git-revision-date-localized-plugin (>=1.2,<2.0)"]
|
||||
imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=9.4,<10.0)"]
|
||||
recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-material-extensions"
|
||||
version = "1.3.1"
|
||||
description = "Extension pack for Python Markdown and MkDocs Material."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"},
|
||||
{file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mozilla-django-oidc"
|
||||
version = "3.0.0"
|
||||
|
@ -2493,6 +2606,16 @@ files = [
|
|||
{file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paginate"
|
||||
version = "0.5.6"
|
||||
description = "Divides large result sets into pages for easier browsing"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "paginate-0.5.6.tar.gz", hash = "sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parameterized"
|
||||
version = "0.8.1"
|
||||
|
@ -2524,13 +2647,13 @@ testing = ["docopt", "pytest (<6.0.0)"]
|
|||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.11.0"
|
||||
version = "0.11.2"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"},
|
||||
{file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"},
|
||||
{file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"},
|
||||
{file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2803,6 +2926,8 @@ files = [
|
|||
{file = "psycopg2-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:426f9f29bde126913a20a96ff8ce7d73fd8a216cfb323b1f04da402d452853c3"},
|
||||
{file = "psycopg2-2.9.9-cp311-cp311-win32.whl", hash = "sha256:ade01303ccf7ae12c356a5e10911c9e1c51136003a9a1d92f7aa9d010fb98372"},
|
||||
{file = "psycopg2-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:121081ea2e76729acfb0673ff33755e8703d45e926e416cb59bae3a86c6a4981"},
|
||||
{file = "psycopg2-2.9.9-cp312-cp312-win32.whl", hash = "sha256:d735786acc7dd25815e89cc4ad529a43af779db2e25aa7c626de864127e5a024"},
|
||||
{file = "psycopg2-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:a7653d00b732afb6fc597e29c50ad28087dcb4fbfb28e86092277a559ae4e693"},
|
||||
{file = "psycopg2-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:5e0d98cade4f0e0304d7d6f25bbfbc5bd186e07b38eac65379309c4ca3193efa"},
|
||||
{file = "psycopg2-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:7e2dacf8b009a1c1e843b5213a87f7c544b2b042476ed7755be813eaf4e8347a"},
|
||||
{file = "psycopg2-2.9.9-cp38-cp38-win32.whl", hash = "sha256:ff432630e510709564c01dafdbe996cb552e0b9f3f065eb89bdce5bd31fabf4c"},
|
||||
|
@ -2923,17 +3048,18 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.15.0"
|
||||
version = "2.17.2"
|
||||
description = "Pygments is a syntax highlighting package written in Python."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "Pygments-2.15.0-py3-none-any.whl", hash = "sha256:77a3299119af881904cd5ecd1ac6a66214b6e9bed1f2db16993b54adede64094"},
|
||||
{file = "Pygments-2.15.0.tar.gz", hash = "sha256:f7e36cffc4c517fbc252861b9a6e4644ca0e5abadf9a113c72d1358ad09b9500"},
|
||||
{file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"},
|
||||
{file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
plugins = ["importlib-metadata"]
|
||||
windows-terminal = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "pylint"
|
||||
|
@ -2992,6 +3118,24 @@ files = [
|
|||
[package.dependencies]
|
||||
pylint = ">=1.7"
|
||||
|
||||
[[package]]
|
||||
name = "pymdown-extensions"
|
||||
version = "10.4"
|
||||
description = "Extension pack for Python Markdown."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pymdown_extensions-10.4-py3-none-any.whl", hash = "sha256:cfc28d6a09d19448bcbf8eee3ce098c7d17ff99f7bd3069db4819af181212037"},
|
||||
{file = "pymdown_extensions-10.4.tar.gz", hash = "sha256:bc46f11749ecd4d6b71cf62396104b4a200bad3498cb0f5dad1b8502fe461a35"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
markdown = ">=3.2"
|
||||
pyyaml = "*"
|
||||
|
||||
[package.extras]
|
||||
extra = ["pygments (>=2.12)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyopenssl"
|
||||
version = "23.2.0"
|
||||
|
@ -3360,6 +3504,20 @@ files = [
|
|||
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml-env-tag"
|
||||
version = "0.1"
|
||||
description = "A custom YAML tag for referencing environment variables in YAML files. "
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
|
||||
{file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pyyaml = "*"
|
||||
|
||||
[[package]]
|
||||
name = "q"
|
||||
version = "2.7"
|
||||
|
@ -3405,6 +3563,103 @@ async-timeout = {version = ">=4.0.2", markers = "python_version <= \"3.11.2\""}
|
|||
hiredis = ["hiredis (>=1.0.0)"]
|
||||
ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2023.10.3"
|
||||
description = "Alternative regular expression module, to replace re."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"},
|
||||
{file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"},
|
||||
{file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"},
|
||||
{file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"},
|
||||
{file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"},
|
||||
{file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"},
|
||||
{file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"},
|
||||
{file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"},
|
||||
{file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"},
|
||||
{file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"},
|
||||
{file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"},
|
||||
{file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"},
|
||||
{file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"},
|
||||
{file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"},
|
||||
{file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"},
|
||||
{file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"},
|
||||
{file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"},
|
||||
{file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"},
|
||||
{file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"},
|
||||
{file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"},
|
||||
{file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"},
|
||||
{file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"},
|
||||
{file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"},
|
||||
{file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"},
|
||||
{file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"},
|
||||
{file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"},
|
||||
{file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"},
|
||||
{file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"},
|
||||
{file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"},
|
||||
{file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"},
|
||||
{file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"},
|
||||
{file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"},
|
||||
{file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"},
|
||||
{file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"},
|
||||
{file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"},
|
||||
{file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"},
|
||||
{file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"},
|
||||
{file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"},
|
||||
{file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"},
|
||||
{file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"},
|
||||
{file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"},
|
||||
{file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"},
|
||||
{file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"},
|
||||
{file = "regex-2023.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a3ee019a9befe84fa3e917a2dd378807e423d013377a884c1970a3c2792d293"},
|
||||
{file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76066d7ff61ba6bf3cb5efe2428fc82aac91802844c022d849a1f0f53820502d"},
|
||||
{file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe50b61bab1b1ec260fa7cd91106fa9fece57e6beba05630afe27c71259c59b"},
|
||||
{file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fd88f373cb71e6b59b7fa597e47e518282455c2734fd4306a05ca219a1991b0"},
|
||||
{file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab05a182c7937fb374f7e946f04fb23a0c0699c0450e9fb02ef567412d2fa3"},
|
||||
{file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dac37cf08fcf2094159922edc7a2784cfcc5c70f8354469f79ed085f0328ebdf"},
|
||||
{file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54ddd0bb8fb626aa1f9ba7b36629564544954fff9669b15da3610c22b9a0991"},
|
||||
{file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3367007ad1951fde612bf65b0dffc8fd681a4ab98ac86957d16491400d661302"},
|
||||
{file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:16f8740eb6dbacc7113e3097b0a36065a02e37b47c936b551805d40340fb9971"},
|
||||
{file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f4f2ca6df64cbdd27f27b34f35adb640b5d2d77264228554e68deda54456eb11"},
|
||||
{file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:39807cbcbe406efca2a233884e169d056c35aa7e9f343d4e78665246a332f597"},
|
||||
{file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7eece6fbd3eae4a92d7c748ae825cbc1ee41a89bb1c3db05b5578ed3cfcfd7cb"},
|
||||
{file = "regex-2023.10.3-cp37-cp37m-win32.whl", hash = "sha256:ce615c92d90df8373d9e13acddd154152645c0dc060871abf6bd43809673d20a"},
|
||||
{file = "regex-2023.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f649fa32fe734c4abdfd4edbb8381c74abf5f34bc0b3271ce687b23729299ed"},
|
||||
{file = "regex-2023.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b98b7681a9437262947f41c7fac567c7e1f6eddd94b0483596d320092004533"},
|
||||
{file = "regex-2023.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:91dc1d531f80c862441d7b66c4505cd6ea9d312f01fb2f4654f40c6fdf5cc37a"},
|
||||
{file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82fcc1f1cc3ff1ab8a57ba619b149b907072e750815c5ba63e7aa2e1163384a4"},
|
||||
{file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7979b834ec7a33aafae34a90aad9f914c41fd6eaa8474e66953f3f6f7cbd4368"},
|
||||
{file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef71561f82a89af6cfcbee47f0fabfdb6e63788a9258e913955d89fdd96902ab"},
|
||||
{file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd829712de97753367153ed84f2de752b86cd1f7a88b55a3a775eb52eafe8a94"},
|
||||
{file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00e871d83a45eee2f8688d7e6849609c2ca2a04a6d48fba3dff4deef35d14f07"},
|
||||
{file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:706e7b739fdd17cb89e1fbf712d9dc21311fc2333f6d435eac2d4ee81985098c"},
|
||||
{file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cc3f1c053b73f20c7ad88b0d1d23be7e7b3901229ce89f5000a8399746a6e039"},
|
||||
{file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f85739e80d13644b981a88f529d79c5bdf646b460ba190bffcaf6d57b2a9863"},
|
||||
{file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:741ba2f511cc9626b7561a440f87d658aabb3d6b744a86a3c025f866b4d19e7f"},
|
||||
{file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e77c90ab5997e85901da85131fd36acd0ed2221368199b65f0d11bca44549711"},
|
||||
{file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:979c24cbefaf2420c4e377ecd1f165ea08cc3d1fbb44bdc51bccbbf7c66a2cb4"},
|
||||
{file = "regex-2023.10.3-cp38-cp38-win32.whl", hash = "sha256:58837f9d221744d4c92d2cf7201c6acd19623b50c643b56992cbd2b745485d3d"},
|
||||
{file = "regex-2023.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:c55853684fe08d4897c37dfc5faeff70607a5f1806c8be148f1695be4a63414b"},
|
||||
{file = "regex-2023.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c54e23836650bdf2c18222c87f6f840d4943944146ca479858404fedeb9f9af"},
|
||||
{file = "regex-2023.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69c0771ca5653c7d4b65203cbfc5e66db9375f1078689459fe196fe08b7b4930"},
|
||||
{file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac965a998e1388e6ff2e9781f499ad1eaa41e962a40d11c7823c9952c77123e"},
|
||||
{file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c0e8fae5b27caa34177bdfa5a960c46ff2f78ee2d45c6db15ae3f64ecadde14"},
|
||||
{file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c56c3d47da04f921b73ff9415fbaa939f684d47293f071aa9cbb13c94afc17d"},
|
||||
{file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ef1e014eed78ab650bef9a6a9cbe50b052c0aebe553fb2881e0453717573f52"},
|
||||
{file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29338556a59423d9ff7b6eb0cb89ead2b0875e08fe522f3e068b955c3e7b59b"},
|
||||
{file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9c6d0ced3c06d0f183b73d3c5920727268d2201aa0fe6d55c60d68c792ff3588"},
|
||||
{file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:994645a46c6a740ee8ce8df7911d4aee458d9b1bc5639bc968226763d07f00fa"},
|
||||
{file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66e2fe786ef28da2b28e222c89502b2af984858091675044d93cb50e6f46d7af"},
|
||||
{file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:11175910f62b2b8c055f2b089e0fedd694fe2be3941b3e2633653bc51064c528"},
|
||||
{file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:06e9abc0e4c9ab4779c74ad99c3fc10d3967d03114449acc2c2762ad4472b8ca"},
|
||||
{file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fb02e4257376ae25c6dd95a5aec377f9b18c09be6ebdefa7ad209b9137b73d48"},
|
||||
{file = "regex-2023.10.3-cp39-cp39-win32.whl", hash = "sha256:3b2c3502603fab52d7619b882c25a6850b766ebd1b18de3df23b2f939360e1bd"},
|
||||
{file = "regex-2023.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:adbccd17dcaff65704c856bd29951c58a1bd4b2b0f8ad6b826dbd543fe740988"},
|
||||
{file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.28.2"
|
||||
|
@ -4237,6 +4492,45 @@ platformdirs = ">=2.4,<4"
|
|||
docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"]
|
||||
test = ["covdefaults (>=2.2.2)", "coverage (>=7.1)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23)", "pytest (>=7.2.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "watchdog"
|
||||
version = "3.0.0"
|
||||
description = "Filesystem events monitoring"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"},
|
||||
{file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"},
|
||||
{file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"},
|
||||
{file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae"},
|
||||
{file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9"},
|
||||
{file = "watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7"},
|
||||
{file = "watchdog-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9fac43a7466eb73e64a9940ac9ed6369baa39b3bf221ae23493a9ec4d0022674"},
|
||||
{file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f"},
|
||||
{file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc"},
|
||||
{file = "watchdog-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3"},
|
||||
{file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3"},
|
||||
{file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0"},
|
||||
{file = "watchdog-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8"},
|
||||
{file = "watchdog-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100"},
|
||||
{file = "watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346"},
|
||||
{file = "watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33"},
|
||||
{file = "watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f"},
|
||||
{file = "watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c"},
|
||||
{file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"},
|
||||
{file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
watchmedo = ["PyYAML (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.2.6"
|
||||
|
@ -4349,6 +4643,16 @@ files = [
|
|||
{file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"},
|
||||
{file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"},
|
||||
{file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"},
|
||||
{file = "wrapt-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ecee4132c6cd2ce5308e21672015ddfed1ff975ad0ac8d27168ea82e71413f55"},
|
||||
{file = "wrapt-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2020f391008ef874c6d9e208b24f28e31bcb85ccff4f335f15a3251d222b92d9"},
|
||||
{file = "wrapt-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2feecf86e1f7a86517cab34ae6c2f081fd2d0dac860cb0c0ded96d799d20b335"},
|
||||
{file = "wrapt-1.14.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:240b1686f38ae665d1b15475966fe0472f78e71b1b4903c143a842659c8e4cb9"},
|
||||
{file = "wrapt-1.14.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9008dad07d71f68487c91e96579c8567c98ca4c3881b9b113bc7b33e9fd78b8"},
|
||||
{file = "wrapt-1.14.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6447e9f3ba72f8e2b985a1da758767698efa72723d5b59accefd716e9e8272bf"},
|
||||
{file = "wrapt-1.14.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:acae32e13a4153809db37405f5eba5bac5fbe2e2ba61ab227926a22901051c0a"},
|
||||
{file = "wrapt-1.14.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49ef582b7a1152ae2766557f0550a9fcbf7bbd76f43fbdc94dd3bf07cc7168be"},
|
||||
{file = "wrapt-1.14.1-cp311-cp311-win32.whl", hash = "sha256:358fe87cc899c6bb0ddc185bf3dbfa4ba646f05b1b0b9b5a27c2cb92c2cea204"},
|
||||
{file = "wrapt-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:26046cd03936ae745a502abf44dac702a5e6880b2b01c29aea8ddf3353b68224"},
|
||||
{file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"},
|
||||
{file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"},
|
||||
{file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"},
|
||||
|
@ -4501,4 +4805,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "cb2a6169c73b017992b840f313e61d62903f5d688032562228c5b965cc2f35c0"
|
||||
content-hash = "e12b50ceae4fcb5c7f726674e75cfa26aeb278a525ba4e332914cb903d1911db"
|
||||
|
|
|
@ -85,6 +85,8 @@ graphene-django = "~3.0.0"
|
|||
django-guardian = "^2.4.0"
|
||||
django-email-bandit = "^2.0"
|
||||
psycopg2 = "^2.9.9"
|
||||
mkdocs = "^1.5.3"
|
||||
mkdocs-material = "^9.4.10"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
ipdb = "^0.13.11"
|
||||
|
|
Загрузка…
Ссылка в новой задаче