├── .gitignore ├── .prettierignore ├── DjangoCon EU 2021 Talk.md ├── DjangoCon EU 2021.pptx ├── README.md ├── reactor ├── manage.py ├── poetry.lock ├── project │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── pyproject.toml └── todo │ ├── __init__.py │ ├── apps.py │ ├── live.py │ ├── migrations │ ├── 0001_initial.py │ └── __init__.py │ ├── models.py │ ├── templates │ └── todo │ │ ├── index.html │ │ └── x-todo.html │ └── views.py ├── sockpuppet ├── manage.py ├── package-lock.json ├── package.json ├── poetry.lock ├── project │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── pyproject.toml ├── todo │ ├── __init__.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── reflexes │ │ ├── __init__.py │ │ └── todo_reflex.py │ ├── templates │ │ └── todo.html │ └── views │ │ ├── __init__.py │ │ └── todo.py └── webpack.config.js ├── unicorn ├── manage.py ├── poetry.lock ├── project │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── pyproject.toml ├── todo │ ├── __init__.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ └── todo │ │ │ └── index.html │ └── views.py └── unicorn │ ├── components │ └── todo.py │ └── templates │ └── unicorn │ └── todo.html └── vuejs ├── LICENSE ├── README.md ├── app.json ├── backend ├── __init__.py ├── api │ ├── __init__.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ └── views.py ├── settings │ ├── __init__.py │ └── dev.py ├── urls.py └── wsgi.py ├── manage.py ├── package.json ├── poetry.lock ├── public ├── index.html └── static │ └── favicon.ico ├── pyproject.toml ├── src ├── App.vue ├── components │ └── Todos.vue ├── main.js ├── router.js ├── services │ ├── api.js │ └── todoService.js └── store │ ├── index.js │ └── modules │ └── todos.js ├── vue.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.html -------------------------------------------------------------------------------- /DjangoCon EU 2021 Talk.md: -------------------------------------------------------------------------------- 1 | # How to create a fullstack, reactive website in Django with absolutely no JavaScript 2 | 3 | ## Intro 4 | 5 | (Front of the field notes notebook) 6 | 7 | Hello! I'm Adam Hill and I'm here to tell you about my journey to find a mythical creature. 8 | 9 | Based on the titles of other talks for this conference and the number of questions I see while lurking in the Django sub-reddit, it appears that others are looking for this mythical creature as well! 10 | 11 | Now... many believed it doesn't really exist. 12 | 13 | But, similar to my search for a friendly Yeti, I'm pretty sure I've at least seen a glimpse of it! 14 | 15 | So, follow along with my field notebook while I document what I find! 16 | 17 | (Waving Yeti saying hello) 18 | 19 | Well, there's one mythical creature, but not exactly what I was looking for! 20 | 21 | The mythical creature I was searching for allows Django developers to build a modern, reactive website without writing _any_ custom JavaScript. 22 | 23 | Don't get me wrong, JavaScript is _fine_. Sometimes it's even "not that bad". 24 | 25 | However, I have run into a few roadblocks trying to integrate a JavaScript frontend with Django. 26 | 27 | There is the paradigm switch, not to mention the syntax differences, from synchronous Python to asynchronous JavaScript. 28 | 29 | There is the high potential for duplicated business logic between the frontend and the backend. 30 | 31 | There is the task of creating a REST or GraphQL API solely for the frontend. 32 | 33 | There is the performance hit on first time page load speeds when the frontend framework is parsed, especially with slower machines or network connections. 34 | 35 | There is making sure the site is SEO-friendly. 36 | 37 | Plus, there is the age-old... 38 | 39 | (Vampire) 40 | 41 | ...keeping up to date with JavaScript build tools. 42 | 43 | I could have just given up on my dream of finding this mythical creature. 44 | 45 | But, I am pretty stubborn. 46 | 47 | I couldn't escape the allure of less complexity, a simpler architecture, faster development time, and... let’s be honest... less code. 48 | 49 | It sounds like a wonderful land of rainbows and sunshine. 50 | 51 | (Mythical land of rainbows and waterfalls) 52 | 53 | *Especially* when some intrepid explorers have already found similar mythical creatures. 54 | 55 | ## Explorers from other lands 56 | 57 | For example, in the land of `Phoenix`, `LiveView` provides frontend interactivity using the backend language, `Elixir`. Because of the robust support for asynchronous processes, it uses websockets to communicate backend changes to the frontend. 58 | 59 | (Phoenix) 60 | 61 | In the land of `Laravel`, `Livewire` provides a frontend framework using `PHP` and regular ole' HTTP calls. It has extensive documentation and screencasts demonstrating all of its capabilities. 62 | 63 | (Leperchaun) 64 | 65 | In the land of `Rails`, `Hotwire` made a splash when it was released. It provides frontend interactivity using `Ruby` and websockets to communicate backend changes to the frontend. 66 | 67 | (Gremlin with wires) 68 | 69 | Some explorers even want to traverse across different lands. `HTMX` which is the successor to `intercooler.js` isn't tied to a particular server-side language and is a lightweight way to provide interactivity without writing any custom JavaScript. Highly recommended! It's really fun to use. 70 | 71 | ## Django 72 | 73 | --> START HERE at HTMX slide 74 | 75 | But, this _is_ DjangoCon, so we should talk about the land of Django, right?! So, let's get CRACKING! 76 | 77 | (Kraken on a computer) 78 | 79 | First, I'll briefly go over some code for a sample Django + Vue.js integration. Then, I'll show a few Django packages that can create a modern, interactive website without writing any JavaScript. 80 | 81 | (django slide overview) 82 | 83 | The packages we'll look at code for are Sockpuppet, reactor, and finally... Unicorn. 84 | 85 | But, first, VueJS. 86 | 87 | ### Django + Vue.js integration 88 | 89 | (VueJS slide) 90 | 91 | I realize that there are lots of ways to integrate Django with a frontend framework, so the specific code doesn't matter too much -- this is more to show an example of what might be required when integrating with Django. React would have different details, but the general gist would be the same. 92 | 93 | Alright, time to live code! 94 | 95 | Nah, I'm just kidding! 96 | 97 | But, we are going to look over this sample todo app I created. 98 | 99 | (Demo VueJS) 100 | 101 | The first thing to realize is that most of the time on your local machine you will have two processes running for your site to render properly. One for the Django backend and one Node process for the frontend. 102 | 103 | #### Django 104 | 105 | This is the typical Django dev server. It provides an API (usually REST or GraphQL). 106 | 107 | It also provides the beloved Django admin to easily create, update and delete data from the database. 108 | 109 | And, lastly, Django serves a static HTML page that contains the shell of the website with navigation, basic site structure, footers, etc., although sometimes Node will handle that. 110 | 111 | Alright, let's look at the code required for the backend. Starting at the urls, we can see that Django is serving the index route and Django REST Framework is providing an "api" route with the `TodoViewSet`. 112 | 113 | Let's look at that view set. It contains a queryset property that returns all todo models from the database serialized with this `TodoSerializer`. 114 | 115 | The serializer uses this `Todo` model and outputs the primary key and the task for the `Todo`. Serializers are also where the validation of business logic would occur in Django REST Framework. 116 | 117 | Django REST Framework creates a nice interface that lets you interact with the API. 118 | 119 | [Show browser with API interface] 120 | 121 | Let's look at all of the current todos. None yet! 122 | 123 | Alright, let's look at the actual site functionality. 124 | 125 | Well, this seems to be missing something. Oh, right! Remember, we need to start up a Node process for things to work as expected? 126 | 127 | #### Node 128 | 129 | The Node process watches the frontend code and transpiles it for browsers to handle. Here it's using `yarn`, but there are about a dozen different ways to do this. 130 | 131 | [yarn serve in terminal] 132 | 133 | [Refresh browser] 134 | 135 | That looks better! 136 | 137 | [Type "Search the ocean", Press "Add"] 138 | 139 | Nice, this looks great! Let's check out the frontend to see what is involved to build out this straight-forward example. 140 | 141 | [VS Code] 142 | 143 | Ok. Here is the index page that is served by Django. You can see it's pretty minimal and contains a `div` element where the application is mounted with the id "app". 144 | 145 | The real magic starts `main.js` where you can see the router, store and render method. 146 | 147 | Let's look at the router first which stipulates what URLs are handled by Vue. This will also override any urls setup in Django. You can see for this example, the root URL is handled by a component named "todos". 148 | 149 | Let's look at the "Todos" component. 150 | 151 | It looks... like HTML, but it has some fun, new attributes. 152 | 153 | For example, this `v-model` is used by Vue to bind this input element to some `task` data. This `@click` binds the button to an `addTodo` JavaScript method with an argument of the input element above. 154 | 155 | And here is how to check for any available `todos` with `v-if` and loop over them with `v-for`. 156 | 157 | It's all in the excellent VueJS documentation, but it's definitely different than the server-side rendered Django templates. 158 | 159 | At the bottom of this component is a reference to the store. It is the central location that retrieves and creates todos. 160 | 161 | It calls this method here [todos.js]. 162 | 163 | Which, in turn, calls a service method. 164 | 165 | Which, finally, calls the backend Django API. 166 | 167 | (VueJS slide) 168 | 169 | Again, this overview isn't meant to explain all the specifics of how Vue works, it's more to show a typical setup when integrating a frontend framework with Django and all of its complexities. 170 | 171 | I haven't even gotten to choosing (and keeping up with) a JavaScript bundler, deciding how to host the backend and frontend, or picking one of the many options to authenticate users. 172 | 173 | Phew! 174 | 175 | #### Benefits 176 | 177 | So, that all sounds very complicated (because it is!), but there some benefits to this approach: it can be useful to split up the responsibilities of a team. It also provides a contract between frontend and backend to communicate to each other. 178 | 179 | And, let's be honest, choosing a mainstream frontend framework that is well documented and supported allows for easier hiring, an ecosystem of libraries that work well with the framework, and the ability to find at least some answers to your pleas for help on StackOverflow. 180 | 181 | ### Other options 182 | 183 | However, if the typical VueJS integration is any mythical creature... maybe it's a Hydra?! There definitely seem like there lots of pieces to wrangle and try to keep track of. 184 | 185 | Unfortunately, I am not Hercules. 186 | 187 | So, I kept searching and found a few other options for Django developers who don't want to wrestle with that additional complexity. 188 | 189 | #### sockpuppet 190 | 191 | The first Django library is `sockpuppet`. 192 | 193 | (Sockpuppet slide) 194 | 195 | It was originally inspired by the Rail's library `StimulusReflex`. It uses the `StimulusJS` library and websockets to communicate changes from the frontend to the backend and vice versa. 196 | 197 | It is based on the concept of a `Reflex` which encompasses the round-trip lifecycle which gets initiated via data attributes in HTML. Let's see how that works with the sample todo app. 198 | 199 | [Show Sockpuppet demo] 200 | 201 | Just like the VueJS example, there is a `Todo` model with one field, `Task`, which will store everything we have to do. 202 | 203 | Alright, let me fire up the Django dev server. Ok, here you can see that sockpuppet is using ASGI and Django Channels under the hood. 204 | 205 | Let's look at the website. Looks pretty similar to the VueJS example. 206 | 207 | Let's try it out. [Type in "Search in the woods", press "Add"] 208 | 209 | Seems to work! 210 | 211 | Now, let's look at the rest of the code. 212 | 213 | `todo.py` contains the view that handles this request. It is wired up like any other class-based view in `urls.py`. And you can see it inherits from `TemplateView` so `get_context_data` should look familiar if you've ever used a class-based view before. 214 | 215 | This is where the initial state for the view is defined and passed into the view context. You can see I'm getting a list of all of the todo objects and it will be available as a template variable with the name "todos". 216 | 217 | Alright, let's look at how that gets rendered in the template. You can use the typical Django template language to loop over each todo and render each task. 218 | 219 | Now, let's look at how new tasks are added. This is where `Reflexes` come in to handle mutations of the current state. First, let's look at the frontend to see how the events are defined. 220 | 221 | You can see this `data-reflex` attribute on the `input` element here. `keyup` is the browser event, `TodoReflex` is the `Reflex` class, and `new_task` is the method that gets called every time a key is pressed in the input. 222 | 223 | And for this `button` element, you can see it is bound to the `add_todo` method. 224 | 225 | Let's look at the code to see how that works. 226 | 227 | You can see the `new_task` method which will get the input element's value and add it to the session. 228 | 229 | The `add_todo` method that gets called when the button is clicked will get the new task from the session, create the `Todo` model, and save it to the database. 230 | 231 | Since we are already rendering all `todos` in a list, when a new `Todo` is saved to the database, it will show up automatically in the list. 232 | 233 | That's pretty magical! 234 | 235 | Let's see the network requests to see a glimpse of how it works behind-the-scenes. You can the websockets connections here in the developer toolbar. And you can even see the method names as part of the websocket packet. 236 | 237 | [End of Sockpuppet demo] 238 | 239 | `Sockpuppet` is well-documented and brings reactive capabilities to Django and a logical approach, while leveraging the battle-tested `StimulusJS` library. 240 | 241 | #### reactor 242 | 243 | Another library that brings interactivity to Django is `Reactor`. 244 | 245 | (reactor slide) 246 | 247 | It uses websockets and its own custom JavaScript library to communicate between the frontend and backend. 248 | 249 | Let's look at a sample todo app for `reactor`. 250 | 251 | [Show demo of reactor] 252 | 253 | You can see the `reactor` demo uses the same `Todo` model with a task field just like the other examples. 254 | 255 | Alright, let me start the Django development server. Again, you can see that it uses ASGI and Django Channels for the websockets which are used to communicate state changes from the frontend to the backend. 256 | 257 | Let's try it out in the browser. 258 | 259 | [Type "Search the mountain", please "Add"] 260 | 261 | Nice! It looks like it's working as expected. 262 | 263 | Now, let's look at the code to see the details. 264 | 265 | Reactor has the concept of "components" that encapsulate some interactive functionality on a website. You can see the `component` templatetag here on the `index.html` page. You can think of the `component` templatetag almost like an include templatetag. Here it is referring to a component named `x-todo`. 266 | 267 | The `x-todo` component should look pretty familiar. It has familiar-looking Django template code and looks likes a regular template. Let's look at how to call methods in our component. 268 | 269 | Here's the input which is listening for the `keyup` browser event. It calls a method for `task` in the component backend and passes along a value named `new_task`. 270 | 271 | Let's also look at the button here. `@click` specifies the browser event to listen to, the `prevent` modifier calls `preventDefault` on the event to cancel it from triggering like usual. 272 | 273 | Now let's look at the backend code. `Reactor` looks for components in a file named `live.py`. Here you can see the `XTodo` component that is the backend code. 274 | 275 | The first thing to note is this `mount` method which loads the initial state of the component when it is rendered. Here we see I'm getting all the `Todos` from the database and setting them to a `todos` variable on an instance of the component. 276 | 277 | The second thing to note is that the methods specified in the frontend will be prepended with "receive_" in the component. This `receive_task` method was specified as `task` by the input element on the frontend. Everytime there is a `keyup` event on that input element it will update this `new_task` variable. 278 | 279 | The `receive_add_todo` method will get called when the "Add" button is clicked from the frontend. The `new_task` instance variable will be used to create a new `Todo` in the database, and then I clear out `new_task` and re-retrieve all of the `Todos` from the database. 280 | 281 | Similarly to `sockpuppet`, the list of todos on the frontend will automatically be updated when `self.todos` is set. 282 | 283 | Let's look at the network requests again to see the websockets network traffic. 284 | 285 | [Type in "Search the lake", press "Add"] 286 | 287 | That's awesome to see how well that works. 288 | 289 | [End demo] 290 | 291 | `Reactor` uses Vue-like attributes for specifying backend actions in frontend HTML. It's very straight-forward to understand what is supposed to happen from the developer-perspective. 292 | 293 | #### Still searching 294 | 295 | Both of these Django libraries are impressive, but they also weren't the mythical creature that *I* was searching for. 296 | 297 | So, while bored at the beginning of the pandemic, I stopped searching and finally created my own project. I named it `Unicorn` (Unicorn) as a homage to the unofficial pony mascot of Django and because of how magical it felt while prototyping the code. 298 | 299 | #### Unicorn 300 | 301 | Let's see how our sample todo app would look like in `Unicorn`. 302 | 303 | (Unicorn demo in browser) 304 | 305 | [Type in "Search the skies", press "Add"] 306 | 307 | Well, this looks _very_ familiar! 308 | 309 | Let's check out the code to see how Unicorn apporoaches interactivity. 310 | 311 | (Unicorn code demo) 312 | 313 | Here is the index page. You can see here that `Unicorn` also has the concept of components. And here I made a component named "todo". 314 | 315 | Let's look at the todo component's template. 316 | 317 | Rendering the current tasks is similar to our other Django solutions where we have access to our normal Django template syntax: if statements, for loops, and anything else you would be able to do with normal Django templates are available. 318 | 319 | Well, let's see how the inputs elements work now. 320 | 321 | This looks pretty familiar, right? Here is the input element for our task and notice the "unicorn:model" attribute. That will specify what field in our backend component will be bound to this input. In this case, the field name will be "task". 322 | 323 | Before looking at the backend, though, let's look at the button real quick. This "unicorn:click" attribute tells `Unicorn` to bind the "add_todo" backend method to the "click" browser event. 324 | 325 | Alright, let's look at how this works in the component's view backend. 326 | 327 | You can see that backend views sub-class `UnicornView`. Under the hood, it sub-classes `TemplateView`, so the process of switching from a standard `TemplateView` classe-based view should be straight-forward. 328 | 329 | Remember when I said that the input element was bound to the "task" field? Here it is on the view. I use types pretty extensively in `Unicorn` to help specify some information about the fields, but they are completely optional. 330 | 331 | Hydrate 332 | 333 | Method 334 | 335 | Ok, let's look at how this works. 336 | 337 | (Open up browser and network inspector) 338 | 339 | You can see here that an HTTP call is firing for each keypress, passing a JSON payload to an endpoint, and then passing a JSON response back. 340 | 341 | If you see that and get concerned about the network traffic latency, `Unicorn` has a few other tricks up its sleeve. 342 | 343 | Bound models can have a `lazy` modifier that only fires an HTTP call when the element loses focus. Or a `defer` modifier that only calls the method when an action event fires. 344 | 345 | I'll show you what I mean in the browser. 346 | 347 | I've slowly added features `Unicorn` over the past year like polling, loading, nested components, and tight integration with Django database models and QuerySets. And because the code is all server-side, unit testing the components is straight-forward and authentication uses the built-in Django functionality. 348 | 349 | (Unicorn slide) 350 | 351 | ## Benefits 352 | 353 | `Unicorn` provides everything I need for a modern website quickly without extra complexity. No custom JavaScript, no additional APIs, no JavaScript bundling, no additional processes to run while developing on local, and no requirement on Django Channels. 354 | 355 | It's quick to add into an existing site and make it feel a little more interactive. 356 | 357 | ## Drawbacks 358 | 359 | The Django projects I have mentioned most likely won't supplant the need for React or Vue anytime soon. However, in my wanderings, most websites don't need anything that complicated. Most sites are well-served by a server-side Django website with some interactivity sprinkled on top. 360 | 361 | If you truly need an SPA, then by all means, definitely stick with one of the major frontend frameworks. 362 | 363 | ## Call to action 364 | 365 | I think I speak for all of the projects mentioned when I say that they are only as helpful as the community around them. 366 | 367 | (Elves) 368 | 369 | I'd love for you to help *any* of these projects any way you can -- staring their repo, trying them out on a new project, creating issues, updating documentation, or even sponsoring the developers. 370 | 371 | ## Fin 372 | 373 | Well, hopefully that was informative and a little bit fun. 374 | 375 | (Fin) 376 | 377 | I'm `adamghill` on GitHub if you want the sample code, slides, or the transcript. And I'm the same username on Twitter. And feel free to reach out if you want to talk about Django, Python, or JavaScript. 378 | 379 | (Yeti waving goodbye) 380 | 381 | I'd also like to thank my co-workers at The Motley Fool who gave feedback on this talk and my lovely wife, Lynn, who did all of the art for the slides. 382 | -------------------------------------------------------------------------------- /DjangoCon EU 2021.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/djangocon-eu-2021-conference-talk/33db4b72a25ed435c94bc75eaba24b5153ae4970/DjangoCon EU 2021.pptx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DjangoCon EU 2021 Conference Talk 2 | 3 | This is a repo for code, slides, transcripts, and other notes for my DjangoCon EU 2021 conference talk: [How to create a full-stack, reactive website in Django with absolutely no JavaScript](https://cfp.2021.djangocon.eu/2021/talk/FJLKVT/). 4 | 5 | ## Assets 6 | 7 | - Powerpoint: https://github.com/adamghill/djangocon-eu-2021-conference-talk/blob/main/DjangoCon%20EU%202021.pptx 8 | - Video: https://www.youtube.com/watch?v=wMiZIK8p6DQ 9 | - Transcript: https://github.com/adamghill/djangocon-eu-2021-conference-talk/blob/main/DjangoCon%20EU%202021%20Talk.md 10 | 11 | ## Django full-stack frameworks 12 | 13 | Example code for Django full-stack frameworks are under each folder in this repo. 14 | 15 | ### vuejs 16 | 17 | Code based on https://github.com/gtalarico/django-vue-template. 18 | 19 | ### Sockpuppet 20 | 21 | - Homepage: https://sockpuppet.argpar.se/ 22 | - Repo: https://github.com/jonathan-s/django-sockpuppet 23 | - Sponsor: https://github.com/sponsors/jonathan-s 24 | 25 | ### reactor 26 | 27 | - Repo: https://github.com/edelvalle/reactor 28 | 29 | ### Unicorn 30 | 31 | - Homepage: https://www.django-unicorn.com/ 32 | - Repo: https://github.com/adamghill/django-unicorn 33 | - Sponsor: https://github.com/sponsors/adamghill 34 | - Article series about how `Unicorn` was built: https://dev.to/adamghill/add-some-magic-to-your-django-website-l8k 35 | 36 | ## Non-Django Python frameworks 37 | 38 | - Flask: [meld](https://github.com/mikeabrahamsen/Flask-Meld) 39 | - [justpy](https://justpy.io/) 40 | 41 | ## Full-stack frameworks in other ecosystems 42 | 43 | - Phoenix: [LiveView](https://github.com/phoenixframework/phoenix_live_view) 44 | - Laravel: [LaravelLivewire](https://laravel-livewire.com/) 45 | - Ruby on Rails: [Hotwire](https://hotwire.dev/) 46 | - [htmx](https://htmx.org/) 47 | - [django-htmx](https://pypi.org/project/django-htmx/) 48 | 49 | ## Thanks 50 | 51 | Thank you to Tim White, Sean Klein, and Hugo Estrada for feedback on early versions of the talk. 52 | -------------------------------------------------------------------------------- /reactor/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /reactor/project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/djangocon-eu-2021-conference-talk/33db4b72a25ed435c94bc75eaba24b5153ae4970/reactor/project/__init__.py -------------------------------------------------------------------------------- /reactor/project/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for project project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") 13 | 14 | from reactor.asgi import get_asgi_application # noqa 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /reactor/project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for project project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.2.3. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "django-insecure-_)9i-9eukk!(exm!)@=ctn(+st9-6j)5^02)-j!x(=ikzktmdg" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | "django.contrib.admin", 35 | "django.contrib.auth", 36 | "django.contrib.contenttypes", 37 | "django.contrib.sessions", 38 | "django.contrib.messages", 39 | "django.contrib.staticfiles", 40 | "channels", # required for websockets 41 | "reactor", # required for reactor 42 | "todo", 43 | ] 44 | 45 | MIDDLEWARE = [ 46 | "django.middleware.security.SecurityMiddleware", 47 | "django.contrib.sessions.middleware.SessionMiddleware", 48 | "django.middleware.common.CommonMiddleware", 49 | "django.middleware.csrf.CsrfViewMiddleware", 50 | "django.contrib.auth.middleware.AuthenticationMiddleware", 51 | "django.contrib.messages.middleware.MessageMiddleware", 52 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 53 | ] 54 | 55 | ROOT_URLCONF = "project.urls" 56 | 57 | TEMPLATES = [ 58 | { 59 | "BACKEND": "django.template.backends.django.DjangoTemplates", 60 | "DIRS": [], 61 | "APP_DIRS": True, 62 | "OPTIONS": { 63 | "context_processors": [ 64 | "django.template.context_processors.debug", 65 | "django.template.context_processors.request", 66 | "django.contrib.auth.context_processors.auth", 67 | "django.contrib.messages.context_processors.messages", 68 | ], 69 | }, 70 | }, 71 | ] 72 | 73 | WSGI_APPLICATION = "project.wsgi.application" 74 | 75 | 76 | # Database 77 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases 78 | 79 | DATABASES = { 80 | "default": { 81 | "ENGINE": "django.db.backends.sqlite3", 82 | "NAME": BASE_DIR / "db.sqlite3", 83 | } 84 | } 85 | 86 | 87 | # Password validation 88 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 89 | 90 | AUTH_PASSWORD_VALIDATORS = [ 91 | { 92 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 93 | }, 94 | { 95 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 96 | }, 97 | { 98 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 99 | }, 100 | { 101 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 102 | }, 103 | ] 104 | 105 | 106 | # Internationalization 107 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 108 | 109 | LANGUAGE_CODE = "en-us" 110 | 111 | TIME_ZONE = "UTC" 112 | 113 | USE_I18N = True 114 | 115 | USE_L10N = True 116 | 117 | USE_TZ = True 118 | 119 | 120 | # Static files (CSS, JavaScript, Images) 121 | # https://docs.djangoproject.com/en/3.2/howto/static-files/ 122 | 123 | STATIC_URL = "/static/" 124 | 125 | # Default primary key field type 126 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 127 | 128 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 129 | 130 | 131 | # Required settings for reactor 132 | 133 | ASGI_APPLICATION = "project.asgi.application" 134 | 135 | CHANNEL_LAYERS = { 136 | "default": {"BACKEND": "channels.layers.InMemoryChannelLayer"}, 137 | } 138 | -------------------------------------------------------------------------------- /reactor/project/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from reactor.channels import ReactorConsumer 4 | 5 | from todo import views 6 | 7 | urlpatterns = [path("", views.index)] 8 | 9 | websocket_urlpatterns = [ 10 | path("__reactor__", ReactorConsumer), 11 | ] 12 | -------------------------------------------------------------------------------- /reactor/project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /reactor/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "reactor-example" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Adam Hill "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.7" 10 | Django = "^3.2.3" 11 | django-reactor = "^2.2.1-beta.0" 12 | channels-redis = "^3.2.0" 13 | 14 | [tool.poetry.dev-dependencies] 15 | poethepoet = "^0.10.0" 16 | black = "^21.5b1" 17 | 18 | [tool.poe.tasks] 19 | m = "./manage.py" 20 | r = "./manage.py runserver 0:8003" 21 | ma = "./manage.py makemigrations" 22 | mi = "./manage.py migrate" 23 | md = ["ma", "mi"] 24 | 25 | [build-system] 26 | requires = ["poetry-core>=1.0.0"] 27 | build-backend = "poetry.core.masonry.api" 28 | -------------------------------------------------------------------------------- /reactor/todo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/djangocon-eu-2021-conference-talk/33db4b72a25ed435c94bc75eaba24b5153ae4970/reactor/todo/__init__.py -------------------------------------------------------------------------------- /reactor/todo/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TodoConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "todo" 7 | -------------------------------------------------------------------------------- /reactor/todo/live.py: -------------------------------------------------------------------------------- 1 | from reactor.component import Component 2 | 3 | from todo.models import Todo 4 | 5 | 6 | class XTodo(Component): 7 | template_name = "todo/x-todo.html" 8 | new_task = "" 9 | 10 | def mount(self, **kwargs): 11 | self.todos = Todo.objects.all() 12 | 13 | def receive_task(self, new_task, **kwargs): 14 | self.new_task = new_task 15 | 16 | def receive_add_todo(self, **kwargs): 17 | todo = Todo(task=self.new_task) 18 | todo.save() 19 | 20 | self.new_task = "" 21 | self.todos = Todo.objects.all() 22 | 23 | def receive_cancel(self, **kwargs): 24 | self.new_task = "" 25 | -------------------------------------------------------------------------------- /reactor/todo/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.3 on 2021-05-15 20:18 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="Todo", 15 | fields=[ 16 | ( 17 | "id", 18 | models.BigAutoField( 19 | auto_created=True, 20 | primary_key=True, 21 | serialize=False, 22 | verbose_name="ID", 23 | ), 24 | ), 25 | ("task", models.CharField(max_length=1024)), 26 | ], 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /reactor/todo/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/djangocon-eu-2021-conference-talk/33db4b72a25ed435c94bc75eaba24b5153ae4970/reactor/todo/migrations/__init__.py -------------------------------------------------------------------------------- /reactor/todo/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Todo(models.Model): 5 | task = models.CharField(max_length=1024) 6 | -------------------------------------------------------------------------------- /reactor/todo/templates/todo/index.html: -------------------------------------------------------------------------------- 1 | {% load reactor %} 2 | 3 | 4 | 5 | 6 | 7 | reactor 8 | 9 | 10 | {% reactor_header %} 11 | 12 | 13 | 14 |
15 |
16 |

17 | reactor 18 |

19 | 20 | {% component 'x-todo' %} 21 |
22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /reactor/todo/templates/todo/x-todo.html: -------------------------------------------------------------------------------- 1 | {% load reactor %} 2 | 3 |
4 |
5 | 6 |
7 | 8 | 9 |
10 | 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 | 29 |
30 |
31 |
32 | 33 | 34 | 35 |
36 |

37 | Current Tasks 38 |

39 | 40 | {% if not todos %} 41 |

None, yet!

42 | {% else %} 43 |
    44 | {% for todo in todos %} 45 |
  • {{ todo.task }}
  • 46 | {% endfor %} 47 |
48 | {% endif %} 49 |
50 | 51 |
52 | -------------------------------------------------------------------------------- /reactor/todo/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | 4 | def index(request): 5 | return render(request, "todo/index.html", {}) 6 | -------------------------------------------------------------------------------- /sockpuppet/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /sockpuppet/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sockpuppet", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "webpack --mode production", 9 | "watch": "webpack --watch --info-verbosity verbose" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "glob": "^7.1.7", 16 | "sockpuppet-js": "^0.6.0", 17 | "stimulus": "^2.0.0", 18 | "stimulus_reflex": "^3.4.1", 19 | "webpack": "^5.37.0", 20 | "webpack-cli": "^4.7.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sockpuppet/poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "aioredis" 3 | version = "1.3.1" 4 | description = "asyncio (PEP 3156) Redis support" 5 | category = "main" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [package.dependencies] 10 | async-timeout = "*" 11 | hiredis = "*" 12 | 13 | [[package]] 14 | name = "appdirs" 15 | version = "1.4.4" 16 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 17 | category = "dev" 18 | optional = false 19 | python-versions = "*" 20 | 21 | [[package]] 22 | name = "asgiref" 23 | version = "3.3.4" 24 | description = "ASGI specs, helper code, and adapters" 25 | category = "main" 26 | optional = false 27 | python-versions = ">=3.6" 28 | 29 | [package.dependencies] 30 | typing-extensions = {version = "*", markers = "python_version < \"3.8\""} 31 | 32 | [package.extras] 33 | tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] 34 | 35 | [[package]] 36 | name = "async-timeout" 37 | version = "3.0.1" 38 | description = "Timeout context manager for asyncio programs" 39 | category = "main" 40 | optional = false 41 | python-versions = ">=3.5.3" 42 | 43 | [[package]] 44 | name = "attrs" 45 | version = "21.2.0" 46 | description = "Classes Without Boilerplate" 47 | category = "main" 48 | optional = false 49 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 50 | 51 | [package.extras] 52 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] 53 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 54 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] 55 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] 56 | 57 | [[package]] 58 | name = "autobahn" 59 | version = "21.3.1" 60 | description = "WebSocket client & server library, WAMP real-time framework" 61 | category = "main" 62 | optional = false 63 | python-versions = ">=3.7" 64 | 65 | [package.dependencies] 66 | cryptography = ">=3.4.6" 67 | hyperlink = ">=21.0.0" 68 | txaio = ">=21.2.1" 69 | 70 | [package.extras] 71 | accelerate = ["wsaccel (>=0.6.3)"] 72 | all = ["zope.interface (>=5.2.0)", "twisted (>=20.3.0)", "attrs (>=20.3.0)", "wsaccel (>=0.6.3)", "python-snappy (>=0.6.0)", "msgpack (>=1.0.2)", "ujson (>=4.0.2)", "cbor2 (>=5.2.0)", "cbor (>=1.0.0)", "py-ubjson (>=0.16.1)", "flatbuffers (>=1.12)", "pyopenssl (>=20.0.1)", "service-identity (>=18.1.0)", "pynacl (>=1.4.0)", "pytrie (>=0.4.0)", "pyqrcode (>=1.2.1)", "cffi (>=1.14.5)", "argon2-cffi (>=20.1.0)", "passlib (>=1.7.4)", "xbr (>=21.2.1)", "zlmdb (>=21.2.1)", "web3 (>=5.16.0)", "jinja2 (>=2.11.3)", "rlp (>=2.0.1)", "py-eth-sig-utils (>=0.4.0)", "py-ecc (>=5.1.0)", "eth-abi (>=2.1.1)", "mnemonic (>=0.19)", "base58 (>=2.1.0)", "ecdsa (>=0.16.1)", "py-multihash (>=2.0.1)"] 73 | compress = ["python-snappy (>=0.6.0)"] 74 | dev = ["pep8-naming (>=0.3.3)", "flake8 (>=2.5.1)", "pyflakes (>=1.0.0)", "pytest (>=2.8.6,<3.3.0)", "twine (>=1.6.5)", "sphinx (>=1.2.3)", "sphinxcontrib-images (>=0.9.2)", "pyenchant (>=1.6.6)", "sphinxcontrib-spelling (>=2.1.2)", "sphinx-rtd-theme (>=0.1.9)", "awscli", "qualname", "passlib", "wheel", "pytest-asyncio (<0.6)", "pytest-aiohttp"] 75 | encryption = ["pyopenssl (>=20.0.1)", "service-identity (>=18.1.0)", "pynacl (>=1.4.0)", "pytrie (>=0.4.0)", "pyqrcode (>=1.2.1)"] 76 | nvx = ["cffi (>=1.14.5)"] 77 | scram = ["cffi (>=1.14.5)", "argon2-cffi (>=20.1.0)", "passlib (>=1.7.4)"] 78 | serialization = ["msgpack (>=1.0.2)", "ujson (>=4.0.2)", "cbor2 (>=5.2.0)", "cbor (>=1.0.0)", "py-ubjson (>=0.16.1)", "flatbuffers (>=1.12)"] 79 | twisted = ["zope.interface (>=5.2.0)", "twisted (>=20.3.0)", "attrs (>=20.3.0)"] 80 | xbr = ["xbr (>=21.2.1)", "cbor2 (>=5.2.0)", "zlmdb (>=21.2.1)", "twisted (>=20.3.0)", "web3 (>=5.16.0)", "jinja2 (>=2.11.3)", "rlp (>=2.0.1)", "py-eth-sig-utils (>=0.4.0)", "py-ecc (>=5.1.0)", "eth-abi (>=2.1.1)", "mnemonic (>=0.19)", "base58 (>=2.1.0)", "ecdsa (>=0.16.1)", "py-multihash (>=2.0.1)"] 81 | 82 | [[package]] 83 | name = "automat" 84 | version = "20.2.0" 85 | description = "Self-service finite-state machines for the programmer on the go." 86 | category = "main" 87 | optional = false 88 | python-versions = "*" 89 | 90 | [package.dependencies] 91 | attrs = ">=19.2.0" 92 | six = "*" 93 | 94 | [package.extras] 95 | visualize = ["graphviz (>0.5.1)", "Twisted (>=16.1.1)"] 96 | 97 | [[package]] 98 | name = "beautifulsoup4" 99 | version = "4.9.3" 100 | description = "Screen-scraping library" 101 | category = "main" 102 | optional = false 103 | python-versions = "*" 104 | 105 | [package.dependencies] 106 | soupsieve = {version = ">1.2", markers = "python_version >= \"3.0\""} 107 | 108 | [package.extras] 109 | html5lib = ["html5lib"] 110 | lxml = ["lxml"] 111 | 112 | [[package]] 113 | name = "black" 114 | version = "21.5b1" 115 | description = "The uncompromising code formatter." 116 | category = "dev" 117 | optional = false 118 | python-versions = ">=3.6.2" 119 | 120 | [package.dependencies] 121 | appdirs = "*" 122 | click = ">=7.1.2" 123 | mypy-extensions = ">=0.4.3" 124 | pathspec = ">=0.8.1,<1" 125 | regex = ">=2020.1.8" 126 | toml = ">=0.10.1" 127 | typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""} 128 | typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} 129 | 130 | [package.extras] 131 | colorama = ["colorama (>=0.4.3)"] 132 | d = ["aiohttp (>=3.6.0)", "aiohttp-cors"] 133 | python2 = ["typed-ast (>=1.4.2)"] 134 | 135 | [[package]] 136 | name = "bs4" 137 | version = "0.0.1" 138 | description = "Dummy package for Beautiful Soup" 139 | category = "main" 140 | optional = false 141 | python-versions = "*" 142 | 143 | [package.dependencies] 144 | beautifulsoup4 = "*" 145 | 146 | [[package]] 147 | name = "cffi" 148 | version = "1.14.5" 149 | description = "Foreign Function Interface for Python calling C code." 150 | category = "main" 151 | optional = false 152 | python-versions = "*" 153 | 154 | [package.dependencies] 155 | pycparser = "*" 156 | 157 | [[package]] 158 | name = "channels" 159 | version = "3.0.3" 160 | description = "Brings async, event-driven capabilities to Django. Django 2.2 and up only." 161 | category = "main" 162 | optional = false 163 | python-versions = ">=3.6" 164 | 165 | [package.dependencies] 166 | asgiref = ">=3.2.10,<4" 167 | daphne = ">=3.0,<4" 168 | Django = ">=2.2" 169 | 170 | [package.extras] 171 | tests = ["pytest", "pytest-django", "pytest-asyncio", "async-generator", "async-timeout", "coverage (>=4.5,<5.0)"] 172 | 173 | [[package]] 174 | name = "channels-redis" 175 | version = "3.2.0" 176 | description = "Redis-backed ASGI channel layer implementation" 177 | category = "main" 178 | optional = false 179 | python-versions = ">=3.6" 180 | 181 | [package.dependencies] 182 | aioredis = ">=1.0,<2.0" 183 | asgiref = ">=3.2.10,<4" 184 | channels = "<4" 185 | msgpack = ">=1.0,<2.0" 186 | 187 | [package.extras] 188 | cryptography = ["cryptography (>=1.3.0)"] 189 | tests = ["cryptography (>=1.3.0)", "pytest", "pytest-asyncio", "async-generator", "async-timeout"] 190 | 191 | [[package]] 192 | name = "click" 193 | version = "8.0.0" 194 | description = "Composable command line interface toolkit" 195 | category = "dev" 196 | optional = false 197 | python-versions = ">=3.6" 198 | 199 | [package.dependencies] 200 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 201 | 202 | [[package]] 203 | name = "colorama" 204 | version = "0.4.4" 205 | description = "Cross-platform colored terminal text." 206 | category = "dev" 207 | optional = false 208 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 209 | 210 | [[package]] 211 | name = "constantly" 212 | version = "15.1.0" 213 | description = "Symbolic constants in Python" 214 | category = "main" 215 | optional = false 216 | python-versions = "*" 217 | 218 | [[package]] 219 | name = "cryptography" 220 | version = "3.4.7" 221 | description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." 222 | category = "main" 223 | optional = false 224 | python-versions = ">=3.6" 225 | 226 | [package.dependencies] 227 | cffi = ">=1.12" 228 | 229 | [package.extras] 230 | docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] 231 | docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] 232 | pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] 233 | sdist = ["setuptools-rust (>=0.11.4)"] 234 | ssh = ["bcrypt (>=3.1.5)"] 235 | test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] 236 | 237 | [[package]] 238 | name = "cssselect" 239 | version = "1.1.0" 240 | description = "cssselect parses CSS3 Selectors and translates them to XPath 1.0" 241 | category = "main" 242 | optional = false 243 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 244 | 245 | [[package]] 246 | name = "daphne" 247 | version = "3.0.2" 248 | description = "Django ASGI (HTTP/WebSocket) server" 249 | category = "main" 250 | optional = false 251 | python-versions = ">=3.6" 252 | 253 | [package.dependencies] 254 | asgiref = ">=3.2.10,<4" 255 | autobahn = ">=0.18" 256 | twisted = {version = ">=18.7", extras = ["tls"]} 257 | 258 | [package.extras] 259 | tests = ["hypothesis (==4.23)", "pytest (>=3.10,<4.0)", "pytest-asyncio (>=0.8,<1.0)"] 260 | 261 | [[package]] 262 | name = "django" 263 | version = "3.2.3" 264 | description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." 265 | category = "main" 266 | optional = false 267 | python-versions = ">=3.6" 268 | 269 | [package.dependencies] 270 | asgiref = ">=3.3.2,<4" 271 | pytz = "*" 272 | sqlparse = ">=0.2.2" 273 | 274 | [package.extras] 275 | argon2 = ["argon2-cffi (>=19.1.0)"] 276 | bcrypt = ["bcrypt"] 277 | 278 | [[package]] 279 | name = "django-sockpuppet" 280 | version = "0.6.0" 281 | description = "Helping you use websockets in an effective way in views" 282 | category = "main" 283 | optional = false 284 | python-versions = "*" 285 | 286 | [package.dependencies] 287 | bs4 = "*" 288 | channels = "*" 289 | channels-redis = "*" 290 | cssselect = {version = "*", optional = true, markers = "extra == \"lxml\""} 291 | lxml = {version = "*", optional = true, markers = "extra == \"lxml\""} 292 | 293 | [package.extras] 294 | lxml = ["lxml", "cssselect"] 295 | 296 | [[package]] 297 | name = "hiredis" 298 | version = "2.0.0" 299 | description = "Python wrapper for hiredis" 300 | category = "main" 301 | optional = false 302 | python-versions = ">=3.6" 303 | 304 | [[package]] 305 | name = "hyperlink" 306 | version = "21.0.0" 307 | description = "A featureful, immutable, and correct URL for Python." 308 | category = "main" 309 | optional = false 310 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 311 | 312 | [package.dependencies] 313 | idna = ">=2.5" 314 | 315 | [[package]] 316 | name = "idna" 317 | version = "3.1" 318 | description = "Internationalized Domain Names in Applications (IDNA)" 319 | category = "main" 320 | optional = false 321 | python-versions = ">=3.4" 322 | 323 | [[package]] 324 | name = "incremental" 325 | version = "21.3.0" 326 | description = "A small library that versions your Python projects." 327 | category = "main" 328 | optional = false 329 | python-versions = "*" 330 | 331 | [package.extras] 332 | scripts = ["click (>=6.0)", "twisted (>=16.4.0)"] 333 | 334 | [[package]] 335 | name = "lxml" 336 | version = "4.6.3" 337 | description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." 338 | category = "main" 339 | optional = false 340 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" 341 | 342 | [package.extras] 343 | cssselect = ["cssselect (>=0.7)"] 344 | html5 = ["html5lib"] 345 | htmlsoup = ["beautifulsoup4"] 346 | source = ["Cython (>=0.29.7)"] 347 | 348 | [[package]] 349 | name = "msgpack" 350 | version = "1.0.2" 351 | description = "MessagePack (de)serializer." 352 | category = "main" 353 | optional = false 354 | python-versions = "*" 355 | 356 | [[package]] 357 | name = "mypy-extensions" 358 | version = "0.4.3" 359 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 360 | category = "dev" 361 | optional = false 362 | python-versions = "*" 363 | 364 | [[package]] 365 | name = "pastel" 366 | version = "0.2.1" 367 | description = "Bring colors to your terminal." 368 | category = "dev" 369 | optional = false 370 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 371 | 372 | [[package]] 373 | name = "pathspec" 374 | version = "0.8.1" 375 | description = "Utility library for gitignore style pattern matching of file paths." 376 | category = "dev" 377 | optional = false 378 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 379 | 380 | [[package]] 381 | name = "poethepoet" 382 | version = "0.10.0" 383 | description = "A task runner that works well with poetry." 384 | category = "dev" 385 | optional = false 386 | python-versions = ">=3.6,<4.0" 387 | 388 | [package.dependencies] 389 | pastel = ">=0.2.0,<0.3.0" 390 | tomlkit = ">=0.6.0,<1.0.0" 391 | 392 | [[package]] 393 | name = "pyasn1" 394 | version = "0.4.8" 395 | description = "ASN.1 types and codecs" 396 | category = "main" 397 | optional = false 398 | python-versions = "*" 399 | 400 | [[package]] 401 | name = "pyasn1-modules" 402 | version = "0.2.8" 403 | description = "A collection of ASN.1-based protocols modules." 404 | category = "main" 405 | optional = false 406 | python-versions = "*" 407 | 408 | [package.dependencies] 409 | pyasn1 = ">=0.4.6,<0.5.0" 410 | 411 | [[package]] 412 | name = "pycparser" 413 | version = "2.20" 414 | description = "C parser in Python" 415 | category = "main" 416 | optional = false 417 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 418 | 419 | [[package]] 420 | name = "pyopenssl" 421 | version = "20.0.1" 422 | description = "Python wrapper module around the OpenSSL library" 423 | category = "main" 424 | optional = false 425 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" 426 | 427 | [package.dependencies] 428 | cryptography = ">=3.2" 429 | six = ">=1.5.2" 430 | 431 | [package.extras] 432 | docs = ["sphinx", "sphinx-rtd-theme"] 433 | test = ["flaky", "pretend", "pytest (>=3.0.1)"] 434 | 435 | [[package]] 436 | name = "pytz" 437 | version = "2021.1" 438 | description = "World timezone definitions, modern and historical" 439 | category = "main" 440 | optional = false 441 | python-versions = "*" 442 | 443 | [[package]] 444 | name = "regex" 445 | version = "2021.4.4" 446 | description = "Alternative regular expression module, to replace re." 447 | category = "dev" 448 | optional = false 449 | python-versions = "*" 450 | 451 | [[package]] 452 | name = "service-identity" 453 | version = "21.1.0" 454 | description = "Service identity verification for pyOpenSSL & cryptography." 455 | category = "main" 456 | optional = false 457 | python-versions = "*" 458 | 459 | [package.dependencies] 460 | attrs = ">=19.1.0" 461 | cryptography = "*" 462 | pyasn1 = "*" 463 | pyasn1-modules = "*" 464 | six = "*" 465 | 466 | [package.extras] 467 | dev = ["coverage[toml] (>=5.0.2)", "pytest", "sphinx", "furo", "idna", "pyopenssl"] 468 | docs = ["sphinx", "furo"] 469 | idna = ["idna"] 470 | tests = ["coverage[toml] (>=5.0.2)", "pytest"] 471 | 472 | [[package]] 473 | name = "six" 474 | version = "1.16.0" 475 | description = "Python 2 and 3 compatibility utilities" 476 | category = "main" 477 | optional = false 478 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 479 | 480 | [[package]] 481 | name = "soupsieve" 482 | version = "2.2.1" 483 | description = "A modern CSS selector implementation for Beautiful Soup." 484 | category = "main" 485 | optional = false 486 | python-versions = ">=3.6" 487 | 488 | [[package]] 489 | name = "sqlparse" 490 | version = "0.4.1" 491 | description = "A non-validating SQL parser." 492 | category = "main" 493 | optional = false 494 | python-versions = ">=3.5" 495 | 496 | [[package]] 497 | name = "toml" 498 | version = "0.10.2" 499 | description = "Python Library for Tom's Obvious, Minimal Language" 500 | category = "dev" 501 | optional = false 502 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 503 | 504 | [[package]] 505 | name = "tomlkit" 506 | version = "0.7.0" 507 | description = "Style preserving TOML library" 508 | category = "dev" 509 | optional = false 510 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 511 | 512 | [[package]] 513 | name = "twisted" 514 | version = "21.2.0" 515 | description = "An asynchronous networking framework written in Python" 516 | category = "main" 517 | optional = false 518 | python-versions = ">=3.5.4" 519 | 520 | [package.dependencies] 521 | attrs = ">=19.2.0" 522 | Automat = ">=0.8.0" 523 | constantly = ">=15.1" 524 | hyperlink = ">=17.1.1" 525 | idna = {version = ">=2.4", optional = true, markers = "extra == \"tls\""} 526 | incremental = ">=16.10.1" 527 | pyopenssl = {version = ">=16.0.0", optional = true, markers = "extra == \"tls\""} 528 | service-identity = {version = ">=18.1.0", optional = true, markers = "extra == \"tls\""} 529 | twisted-iocpsupport = {version = ">=1.0.0,<1.1.0", markers = "platform_system == \"Windows\""} 530 | "zope.interface" = ">=4.4.2" 531 | 532 | [package.extras] 533 | all_non_platform = ["cython-test-exception-raiser (>=1.0,<2.0)", "PyHamcrest (>=1.9.0)", "pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)", "pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)", "contextvars (>=2.4,<3)"] 534 | conch = ["pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)"] 535 | contextvars = ["contextvars (>=2.4,<3)"] 536 | dev = ["towncrier (>=17.4.0)", "sphinx-rtd-theme (>=0.5.0,<0.6.0)", "readthedocs-sphinx-ext (>=2.1,<3.0)", "sphinx (>=3.3,<4.0)", "pyflakes (>=1.0.0)", "python-subunit", "twistedchecker (>=0.7.2)", "pydoctor (>=20.12.1)"] 537 | dev_release = ["towncrier (>=17.4.0)", "sphinx-rtd-theme (>=0.5.0,<0.6.0)", "readthedocs-sphinx-ext (>=2.1,<3.0)", "sphinx (>=3.3,<4.0)", "pydoctor (>=20.12.1)"] 538 | http2 = ["h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)"] 539 | macos_platform = ["pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "cython-test-exception-raiser (>=1.0,<2.0)", "PyHamcrest (>=1.9.0)", "pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)", "pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)", "contextvars (>=2.4,<3)"] 540 | osx_platform = ["pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "cython-test-exception-raiser (>=1.0,<2.0)", "PyHamcrest (>=1.9.0)", "pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)", "pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)", "contextvars (>=2.4,<3)"] 541 | serial = ["pyserial (>=3.0)", "pywin32 (!=226)"] 542 | test = ["cython-test-exception-raiser (>=1.0,<2.0)", "PyHamcrest (>=1.9.0)"] 543 | tls = ["pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)"] 544 | windows_platform = ["pywin32 (!=226)", "cython-test-exception-raiser (>=1.0,<2.0)", "PyHamcrest (>=1.9.0)", "pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)", "pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)", "contextvars (>=2.4,<3)"] 545 | 546 | [[package]] 547 | name = "twisted-iocpsupport" 548 | version = "1.0.1" 549 | description = "An extension for use in the twisted I/O Completion Ports reactor." 550 | category = "main" 551 | optional = false 552 | python-versions = "*" 553 | 554 | [[package]] 555 | name = "txaio" 556 | version = "21.2.1" 557 | description = "Compatibility API between asyncio/Twisted/Trollius" 558 | category = "main" 559 | optional = false 560 | python-versions = ">=3.6" 561 | 562 | [package.extras] 563 | all = ["zope.interface (>=5.2.0)", "twisted (>=20.3.0)"] 564 | dev = ["wheel", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "pep8 (>=1.6.2)", "sphinx (>=1.2.3)", "pyenchant (>=1.6.6)", "sphinxcontrib-spelling (>=2.1.2)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.1.1)", "mock (==1.3.0)", "twine (>=1.6.5)", "tox-gh-actions (>=2.2.0)"] 565 | twisted = ["zope.interface (>=5.2.0)", "twisted (>=20.3.0)"] 566 | 567 | [[package]] 568 | name = "typed-ast" 569 | version = "1.4.3" 570 | description = "a fork of Python 2 and 3 ast modules with type comment support" 571 | category = "dev" 572 | optional = false 573 | python-versions = "*" 574 | 575 | [[package]] 576 | name = "typing-extensions" 577 | version = "3.10.0.0" 578 | description = "Backported and Experimental Type Hints for Python 3.5+" 579 | category = "main" 580 | optional = false 581 | python-versions = "*" 582 | 583 | [[package]] 584 | name = "zope.interface" 585 | version = "5.4.0" 586 | description = "Interfaces for Python" 587 | category = "main" 588 | optional = false 589 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 590 | 591 | [package.extras] 592 | docs = ["sphinx", "repoze.sphinx.autointerface"] 593 | test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] 594 | testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] 595 | 596 | [metadata] 597 | lock-version = "1.1" 598 | python-versions = "^3.7" 599 | content-hash = "6679a8c40569c0f59a6fb39edf30398af1caf740d239c2df7fa8f8d95ace8d81" 600 | 601 | [metadata.files] 602 | aioredis = [ 603 | {file = "aioredis-1.3.1-py3-none-any.whl", hash = "sha256:b61808d7e97b7cd5a92ed574937a079c9387fdadd22bfbfa7ad2fd319ecc26e3"}, 604 | {file = "aioredis-1.3.1.tar.gz", hash = "sha256:15f8af30b044c771aee6787e5ec24694c048184c7b9e54c3b60c750a4b93273a"}, 605 | ] 606 | appdirs = [ 607 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 608 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 609 | ] 610 | asgiref = [ 611 | {file = "asgiref-3.3.4-py3-none-any.whl", hash = "sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee"}, 612 | {file = "asgiref-3.3.4.tar.gz", hash = "sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78"}, 613 | ] 614 | async-timeout = [ 615 | {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, 616 | {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, 617 | ] 618 | attrs = [ 619 | {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, 620 | {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, 621 | ] 622 | autobahn = [ 623 | {file = "autobahn-21.3.1-py2.py3-none-any.whl", hash = "sha256:9195df8af03b0ff29ccd4b7f5abbde957ee90273465942205f9a1bad6c3f07ac"}, 624 | {file = "autobahn-21.3.1.tar.gz", hash = "sha256:e126c1f583e872fb59e79d36977cfa1f2d0a8a79f90ae31f406faae7664b8e03"}, 625 | ] 626 | automat = [ 627 | {file = "Automat-20.2.0-py2.py3-none-any.whl", hash = "sha256:b6feb6455337df834f6c9962d6ccf771515b7d939bca142b29c20c2376bc6111"}, 628 | {file = "Automat-20.2.0.tar.gz", hash = "sha256:7979803c74610e11ef0c0d68a2942b152df52da55336e0c9d58daf1831cbdf33"}, 629 | ] 630 | beautifulsoup4 = [ 631 | {file = "beautifulsoup4-4.9.3-py2-none-any.whl", hash = "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35"}, 632 | {file = "beautifulsoup4-4.9.3-py3-none-any.whl", hash = "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"}, 633 | {file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"}, 634 | ] 635 | black = [ 636 | {file = "black-21.5b1-py3-none-any.whl", hash = "sha256:8a60071a0043876a4ae96e6c69bd3a127dad2c1ca7c8083573eb82f92705d008"}, 637 | {file = "black-21.5b1.tar.gz", hash = "sha256:23695358dbcb3deafe7f0a3ad89feee5999a46be5fec21f4f1d108be0bcdb3b1"}, 638 | ] 639 | bs4 = [ 640 | {file = "bs4-0.0.1.tar.gz", hash = "sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a"}, 641 | ] 642 | cffi = [ 643 | {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, 644 | {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, 645 | {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, 646 | {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, 647 | {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, 648 | {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, 649 | {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, 650 | {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, 651 | {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, 652 | {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, 653 | {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, 654 | {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, 655 | {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, 656 | {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, 657 | {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, 658 | {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, 659 | {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, 660 | {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, 661 | {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, 662 | {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, 663 | {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, 664 | {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, 665 | {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, 666 | {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, 667 | {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, 668 | {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, 669 | {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, 670 | {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, 671 | {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, 672 | {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, 673 | {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, 674 | {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, 675 | {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, 676 | {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, 677 | {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, 678 | {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, 679 | {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, 680 | ] 681 | channels = [ 682 | {file = "channels-3.0.3-py3-none-any.whl", hash = "sha256:3f15bdd2138bb4796e76ea588a0a344b12a7964ea9b2e456f992fddb988a4317"}, 683 | {file = "channels-3.0.3.tar.gz", hash = "sha256:056b72e51080a517a0f33a0a30003e03833b551d75394d6636c885d4edb8188f"}, 684 | ] 685 | channels-redis = [ 686 | {file = "channels_redis-3.2.0-py2.py3-none-any.whl", hash = "sha256:18d63f6462a58011740dc8eeb57ea4b31ec220eb551cb71b27de9c6779a549de"}, 687 | {file = "channels_redis-3.2.0.tar.gz", hash = "sha256:2fb31a63b05373f6402da2e6a91a22b9e66eb8b56626c6bfc93e156c734c5ae6"}, 688 | ] 689 | click = [ 690 | {file = "click-8.0.0-py3-none-any.whl", hash = "sha256:e90e62ced43dc8105fb9a26d62f0d9340b5c8db053a814e25d95c19873ae87db"}, 691 | {file = "click-8.0.0.tar.gz", hash = "sha256:7d8c289ee437bcb0316820ccee14aefcb056e58d31830ecab8e47eda6540e136"}, 692 | ] 693 | colorama = [ 694 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 695 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 696 | ] 697 | constantly = [ 698 | {file = "constantly-15.1.0-py2.py3-none-any.whl", hash = "sha256:dd2fa9d6b1a51a83f0d7dd76293d734046aa176e384bf6e33b7e44880eb37c5d"}, 699 | {file = "constantly-15.1.0.tar.gz", hash = "sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35"}, 700 | ] 701 | cryptography = [ 702 | {file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"}, 703 | {file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"}, 704 | {file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"}, 705 | {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"}, 706 | {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"}, 707 | {file = "cryptography-3.4.7-cp36-abi3-win32.whl", hash = "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d"}, 708 | {file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"}, 709 | {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"}, 710 | {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"}, 711 | {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"}, 712 | {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"}, 713 | {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"}, 714 | ] 715 | cssselect = [ 716 | {file = "cssselect-1.1.0-py2.py3-none-any.whl", hash = "sha256:f612ee47b749c877ebae5bb77035d8f4202c6ad0f0fc1271b3c18ad6c4468ecf"}, 717 | {file = "cssselect-1.1.0.tar.gz", hash = "sha256:f95f8dedd925fd8f54edb3d2dfb44c190d9d18512377d3c1e2388d16126879bc"}, 718 | ] 719 | daphne = [ 720 | {file = "daphne-3.0.2-py3-none-any.whl", hash = "sha256:a9af943c79717bc52fe64a3c236ae5d3adccc8b5be19c881b442d2c3db233393"}, 721 | {file = "daphne-3.0.2.tar.gz", hash = "sha256:76ffae916ba3aa66b46996c14fa713e46004788167a4873d647544e750e0e99f"}, 722 | ] 723 | django = [ 724 | {file = "Django-3.2.3-py3-none-any.whl", hash = "sha256:7e0a1393d18c16b503663752a8b6790880c5084412618990ce8a81cc908b4962"}, 725 | {file = "Django-3.2.3.tar.gz", hash = "sha256:13ac78dbfd189532cad8f383a27e58e18b3d33f80009ceb476d7fcbfc5dcebd8"}, 726 | ] 727 | django-sockpuppet = [ 728 | {file = "django-sockpuppet-0.6.0.tar.gz", hash = "sha256:a0f90239d855026876edcd3d4d835b3dca7f71579bb56d0de3ec39bf348929b3"}, 729 | {file = "django_sockpuppet-0.6.0-py2.py3-none-any.whl", hash = "sha256:478b7ef9936cf3914c08ee507abaa1daf7d5f2d9bea9efff3677d1d5b12e82e9"}, 730 | ] 731 | hiredis = [ 732 | {file = "hiredis-2.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b4c8b0bc5841e578d5fb32a16e0c305359b987b850a06964bd5a62739d688048"}, 733 | {file = "hiredis-2.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0adea425b764a08270820531ec2218d0508f8ae15a448568109ffcae050fee26"}, 734 | {file = "hiredis-2.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3d55e36715ff06cdc0ab62f9591607c4324297b6b6ce5b58cb9928b3defe30ea"}, 735 | {file = "hiredis-2.0.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:5d2a48c80cf5a338d58aae3c16872f4d452345e18350143b3bf7216d33ba7b99"}, 736 | {file = "hiredis-2.0.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:240ce6dc19835971f38caf94b5738092cb1e641f8150a9ef9251b7825506cb05"}, 737 | {file = "hiredis-2.0.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:5dc7a94bb11096bc4bffd41a3c4f2b958257085c01522aa81140c68b8bf1630a"}, 738 | {file = "hiredis-2.0.0-cp36-cp36m-win32.whl", hash = "sha256:139705ce59d94eef2ceae9fd2ad58710b02aee91e7fa0ccb485665ca0ecbec63"}, 739 | {file = "hiredis-2.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c39c46d9e44447181cd502a35aad2bb178dbf1b1f86cf4db639d7b9614f837c6"}, 740 | {file = "hiredis-2.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:adf4dd19d8875ac147bf926c727215a0faf21490b22c053db464e0bf0deb0485"}, 741 | {file = "hiredis-2.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0f41827028901814c709e744060843c77e78a3aca1e0d6875d2562372fcb405a"}, 742 | {file = "hiredis-2.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:508999bec4422e646b05c95c598b64bdbef1edf0d2b715450a078ba21b385bcc"}, 743 | {file = "hiredis-2.0.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:0d5109337e1db373a892fdcf78eb145ffb6bbd66bb51989ec36117b9f7f9b579"}, 744 | {file = "hiredis-2.0.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:04026461eae67fdefa1949b7332e488224eac9e8f2b5c58c98b54d29af22093e"}, 745 | {file = "hiredis-2.0.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a00514362df15af041cc06e97aebabf2895e0a7c42c83c21894be12b84402d79"}, 746 | {file = "hiredis-2.0.0-cp37-cp37m-win32.whl", hash = "sha256:09004096e953d7ebd508cded79f6b21e05dff5d7361771f59269425108e703bc"}, 747 | {file = "hiredis-2.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a"}, 748 | {file = "hiredis-2.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:294a6697dfa41a8cba4c365dd3715abc54d29a86a40ec6405d677ca853307cfb"}, 749 | {file = "hiredis-2.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3dddf681284fe16d047d3ad37415b2e9ccdc6c8986c8062dbe51ab9a358b50a5"}, 750 | {file = "hiredis-2.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:dcef843f8de4e2ff5e35e96ec2a4abbdf403bd0f732ead127bd27e51f38ac298"}, 751 | {file = "hiredis-2.0.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:87c7c10d186f1743a8fd6a971ab6525d60abd5d5d200f31e073cd5e94d7e7a9d"}, 752 | {file = "hiredis-2.0.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:7f0055f1809b911ab347a25d786deff5e10e9cf083c3c3fd2dd04e8612e8d9db"}, 753 | {file = "hiredis-2.0.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:11d119507bb54e81f375e638225a2c057dda748f2b1deef05c2b1a5d42686048"}, 754 | {file = "hiredis-2.0.0-cp38-cp38-win32.whl", hash = "sha256:7492af15f71f75ee93d2a618ca53fea8be85e7b625e323315169977fae752426"}, 755 | {file = "hiredis-2.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:65d653df249a2f95673976e4e9dd7ce10de61cfc6e64fa7eeaa6891a9559c581"}, 756 | {file = "hiredis-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae8427a5e9062ba66fc2c62fb19a72276cf12c780e8db2b0956ea909c48acff5"}, 757 | {file = "hiredis-2.0.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:3f5f7e3a4ab824e3de1e1700f05ad76ee465f5f11f5db61c4b297ec29e692b2e"}, 758 | {file = "hiredis-2.0.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:e3447d9e074abf0e3cd85aef8131e01ab93f9f0e86654db7ac8a3f73c63706ce"}, 759 | {file = "hiredis-2.0.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:8b42c0dc927b8d7c0eb59f97e6e34408e53bc489f9f90e66e568f329bff3e443"}, 760 | {file = "hiredis-2.0.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:b84f29971f0ad4adaee391c6364e6f780d5aae7e9226d41964b26b49376071d0"}, 761 | {file = "hiredis-2.0.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0b39ec237459922c6544d071cdcf92cbb5bc6685a30e7c6d985d8a3e3a75326e"}, 762 | {file = "hiredis-2.0.0-cp39-cp39-win32.whl", hash = "sha256:a7928283143a401e72a4fad43ecc85b35c27ae699cf5d54d39e1e72d97460e1d"}, 763 | {file = "hiredis-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:a4ee8000454ad4486fb9f28b0cab7fa1cd796fc36d639882d0b34109b5b3aec9"}, 764 | {file = "hiredis-2.0.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1f03d4dadd595f7a69a75709bc81902673fa31964c75f93af74feac2f134cc54"}, 765 | {file = "hiredis-2.0.0-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:04927a4c651a0e9ec11c68e4427d917e44ff101f761cd3b5bc76f86aaa431d27"}, 766 | {file = "hiredis-2.0.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:a39efc3ade8c1fb27c097fd112baf09d7fd70b8cb10ef1de4da6efbe066d381d"}, 767 | {file = "hiredis-2.0.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:07bbf9bdcb82239f319b1f09e8ef4bdfaec50ed7d7ea51a56438f39193271163"}, 768 | {file = "hiredis-2.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:807b3096205c7cec861c8803a6738e33ed86c9aae76cac0e19454245a6bbbc0a"}, 769 | {file = "hiredis-2.0.0-pp37-pypy37_pp73-manylinux1_x86_64.whl", hash = "sha256:1233e303645f468e399ec906b6b48ab7cd8391aae2d08daadbb5cad6ace4bd87"}, 770 | {file = "hiredis-2.0.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:cb2126603091902767d96bcb74093bd8b14982f41809f85c9b96e519c7e1dc41"}, 771 | {file = "hiredis-2.0.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0"}, 772 | {file = "hiredis-2.0.0.tar.gz", hash = "sha256:81d6d8e39695f2c37954d1011c0480ef7cf444d4e3ae24bc5e89ee5de360139a"}, 773 | ] 774 | hyperlink = [ 775 | {file = "hyperlink-21.0.0-py2.py3-none-any.whl", hash = "sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4"}, 776 | {file = "hyperlink-21.0.0.tar.gz", hash = "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b"}, 777 | ] 778 | idna = [ 779 | {file = "idna-3.1-py3-none-any.whl", hash = "sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16"}, 780 | {file = "idna-3.1.tar.gz", hash = "sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1"}, 781 | ] 782 | incremental = [ 783 | {file = "incremental-21.3.0-py2.py3-none-any.whl", hash = "sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321"}, 784 | {file = "incremental-21.3.0.tar.gz", hash = "sha256:02f5de5aff48f6b9f665d99d48bfc7ec03b6e3943210de7cfc88856d755d6f57"}, 785 | ] 786 | lxml = [ 787 | {file = "lxml-4.6.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:df7c53783a46febb0e70f6b05df2ba104610f2fb0d27023409734a3ecbb78fb2"}, 788 | {file = "lxml-4.6.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1b7584d421d254ab86d4f0b13ec662a9014397678a7c4265a02a6d7c2b18a75f"}, 789 | {file = "lxml-4.6.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:079f3ae844f38982d156efce585bc540c16a926d4436712cf4baee0cce487a3d"}, 790 | {file = "lxml-4.6.3-cp27-cp27m-win32.whl", hash = "sha256:bc4313cbeb0e7a416a488d72f9680fffffc645f8a838bd2193809881c67dd106"}, 791 | {file = "lxml-4.6.3-cp27-cp27m-win_amd64.whl", hash = "sha256:8157dadbb09a34a6bd95a50690595e1fa0af1a99445e2744110e3dca7831c4ee"}, 792 | {file = "lxml-4.6.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7728e05c35412ba36d3e9795ae8995e3c86958179c9770e65558ec3fdfd3724f"}, 793 | {file = "lxml-4.6.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4bff24dfeea62f2e56f5bab929b4428ae6caba2d1eea0c2d6eb618e30a71e6d4"}, 794 | {file = "lxml-4.6.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:74f7d8d439b18fa4c385f3f5dfd11144bb87c1da034a466c5b5577d23a1d9b51"}, 795 | {file = "lxml-4.6.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f90ba11136bfdd25cae3951af8da2e95121c9b9b93727b1b896e3fa105b2f586"}, 796 | {file = "lxml-4.6.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:4c61b3a0db43a1607d6264166b230438f85bfed02e8cff20c22e564d0faff354"}, 797 | {file = "lxml-4.6.3-cp35-cp35m-manylinux2014_x86_64.whl", hash = "sha256:5c8c163396cc0df3fd151b927e74f6e4acd67160d6c33304e805b84293351d16"}, 798 | {file = "lxml-4.6.3-cp35-cp35m-win32.whl", hash = "sha256:f2380a6376dfa090227b663f9678150ef27543483055cc327555fb592c5967e2"}, 799 | {file = "lxml-4.6.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c4f05c5a7c49d2fb70223d0d5bcfbe474cf928310ac9fa6a7c6dddc831d0b1d4"}, 800 | {file = "lxml-4.6.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d2e35d7bf1c1ac8c538f88d26b396e73dd81440d59c1ef8522e1ea77b345ede4"}, 801 | {file = "lxml-4.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:289e9ca1a9287f08daaf796d96e06cb2bc2958891d7911ac7cae1c5f9e1e0ee3"}, 802 | {file = "lxml-4.6.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bccbfc27563652de7dc9bdc595cb25e90b59c5f8e23e806ed0fd623755b6565d"}, 803 | {file = "lxml-4.6.3-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d916d31fd85b2f78c76400d625076d9124de3e4bda8b016d25a050cc7d603f24"}, 804 | {file = "lxml-4.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:820628b7b3135403540202e60551e741f9b6d3304371712521be939470b454ec"}, 805 | {file = "lxml-4.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:c47ff7e0a36d4efac9fd692cfa33fbd0636674c102e9e8d9b26e1b93a94e7617"}, 806 | {file = "lxml-4.6.3-cp36-cp36m-win32.whl", hash = "sha256:5a0a14e264069c03e46f926be0d8919f4105c1623d620e7ec0e612a2e9bf1c04"}, 807 | {file = "lxml-4.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:92e821e43ad382332eade6812e298dc9701c75fe289f2a2d39c7960b43d1e92a"}, 808 | {file = "lxml-4.6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:efd7a09678fd8b53117f6bae4fa3825e0a22b03ef0a932e070c0bdbb3a35e654"}, 809 | {file = "lxml-4.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:efac139c3f0bf4f0939f9375af4b02c5ad83a622de52d6dfa8e438e8e01d0eb0"}, 810 | {file = "lxml-4.6.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0fbcf5565ac01dff87cbfc0ff323515c823081c5777a9fc7703ff58388c258c3"}, 811 | {file = "lxml-4.6.3-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:36108c73739985979bf302006527cf8a20515ce444ba916281d1c43938b8bb96"}, 812 | {file = "lxml-4.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:122fba10466c7bd4178b07dba427aa516286b846b2cbd6f6169141917283aae2"}, 813 | {file = "lxml-4.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:cdaf11d2bd275bf391b5308f86731e5194a21af45fbaaaf1d9e8147b9160ea92"}, 814 | {file = "lxml-4.6.3-cp37-cp37m-win32.whl", hash = "sha256:3439c71103ef0e904ea0a1901611863e51f50b5cd5e8654a151740fde5e1cade"}, 815 | {file = "lxml-4.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4289728b5e2000a4ad4ab8da6e1db2e093c63c08bdc0414799ee776a3f78da4b"}, 816 | {file = "lxml-4.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b007cbb845b28db4fb8b6a5cdcbf65bacb16a8bd328b53cbc0698688a68e1caa"}, 817 | {file = "lxml-4.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:76fa7b1362d19f8fbd3e75fe2fb7c79359b0af8747e6f7141c338f0bee2f871a"}, 818 | {file = "lxml-4.6.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:26e761ab5b07adf5f555ee82fb4bfc35bf93750499c6c7614bd64d12aaa67927"}, 819 | {file = "lxml-4.6.3-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:e1cbd3f19a61e27e011e02f9600837b921ac661f0c40560eefb366e4e4fb275e"}, 820 | {file = "lxml-4.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:66e575c62792c3f9ca47cb8b6fab9e35bab91360c783d1606f758761810c9791"}, 821 | {file = "lxml-4.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:1b38116b6e628118dea5b2186ee6820ab138dbb1e24a13e478490c7db2f326ae"}, 822 | {file = "lxml-4.6.3-cp38-cp38-win32.whl", hash = "sha256:89b8b22a5ff72d89d48d0e62abb14340d9e99fd637d046c27b8b257a01ffbe28"}, 823 | {file = "lxml-4.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:2a9d50e69aac3ebee695424f7dbd7b8c6d6eb7de2a2eb6b0f6c7db6aa41e02b7"}, 824 | {file = "lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ce256aaa50f6cc9a649c51be3cd4ff142d67295bfc4f490c9134d0f9f6d58ef0"}, 825 | {file = "lxml-4.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:7610b8c31688f0b1be0ef882889817939490a36d0ee880ea562a4e1399c447a1"}, 826 | {file = "lxml-4.6.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f8380c03e45cf09f8557bdaa41e1fa7c81f3ae22828e1db470ab2a6c96d8bc23"}, 827 | {file = "lxml-4.6.3-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:3082c518be8e97324390614dacd041bb1358c882d77108ca1957ba47738d9d59"}, 828 | {file = "lxml-4.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:884ab9b29feaca361f7f88d811b1eea9bfca36cf3da27768d28ad45c3ee6f969"}, 829 | {file = "lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:6f12e1427285008fd32a6025e38e977d44d6382cf28e7201ed10d6c1698d2a9a"}, 830 | {file = "lxml-4.6.3-cp39-cp39-win32.whl", hash = "sha256:33bb934a044cf32157c12bfcfbb6649807da20aa92c062ef51903415c704704f"}, 831 | {file = "lxml-4.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:542d454665a3e277f76954418124d67516c5f88e51a900365ed54a9806122b83"}, 832 | {file = "lxml-4.6.3.tar.gz", hash = "sha256:39b78571b3b30645ac77b95f7c69d1bffc4cf8c3b157c435a34da72e78c82468"}, 833 | ] 834 | msgpack = [ 835 | {file = "msgpack-1.0.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:b6d9e2dae081aa35c44af9c4298de4ee72991305503442a5c74656d82b581fe9"}, 836 | {file = "msgpack-1.0.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a99b144475230982aee16b3d249170f1cccebf27fb0a08e9f603b69637a62192"}, 837 | {file = "msgpack-1.0.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1026dcc10537d27dd2d26c327e552f05ce148977e9d7b9f1718748281b38c841"}, 838 | {file = "msgpack-1.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:fe07bc6735d08e492a327f496b7850e98cb4d112c56df69b0c844dbebcbb47f6"}, 839 | {file = "msgpack-1.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9ea52fff0473f9f3000987f313310208c879493491ef3ccf66268eff8d5a0326"}, 840 | {file = "msgpack-1.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:26a1759f1a88df5f1d0b393eb582ec022326994e311ba9c5818adc5374736439"}, 841 | {file = "msgpack-1.0.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:497d2c12426adcd27ab83144057a705efb6acc7e85957a51d43cdcf7f258900f"}, 842 | {file = "msgpack-1.0.2-cp36-cp36m-win32.whl", hash = "sha256:e89ec55871ed5473a041c0495b7b4e6099f6263438e0bd04ccd8418f92d5d7f2"}, 843 | {file = "msgpack-1.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a4355d2193106c7aa77c98fc955252a737d8550320ecdb2e9ac701e15e2943bc"}, 844 | {file = "msgpack-1.0.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:d6c64601af8f3893d17ec233237030e3110f11b8a962cb66720bf70c0141aa54"}, 845 | {file = "msgpack-1.0.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f484cd2dca68502de3704f056fa9b318c94b1539ed17a4c784266df5d6978c87"}, 846 | {file = "msgpack-1.0.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f3e6aaf217ac1c7ce1563cf52a2f4f5d5b1f64e8729d794165db71da57257f0c"}, 847 | {file = "msgpack-1.0.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:8521e5be9e3b93d4d5e07cb80b7e32353264d143c1f072309e1863174c6aadb1"}, 848 | {file = "msgpack-1.0.2-cp37-cp37m-win32.whl", hash = "sha256:31c17bbf2ae5e29e48d794c693b7ca7a0c73bd4280976d408c53df421e838d2a"}, 849 | {file = "msgpack-1.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8ffb24a3b7518e843cd83538cf859e026d24ec41ac5721c18ed0c55101f9775b"}, 850 | {file = "msgpack-1.0.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:b28c0876cce1466d7c2195d7658cf50e4730667196e2f1355c4209444717ee06"}, 851 | {file = "msgpack-1.0.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:87869ba567fe371c4555d2e11e4948778ab6b59d6cc9d8460d543e4cfbbddd1c"}, 852 | {file = "msgpack-1.0.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b55f7db883530b74c857e50e149126b91bb75d35c08b28db12dcb0346f15e46e"}, 853 | {file = "msgpack-1.0.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:ac25f3e0513f6673e8b405c3a80500eb7be1cf8f57584be524c4fa78fe8e0c83"}, 854 | {file = "msgpack-1.0.2-cp38-cp38-win32.whl", hash = "sha256:0cb94ee48675a45d3b86e61d13c1e6f1696f0183f0715544976356ff86f741d9"}, 855 | {file = "msgpack-1.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:e36a812ef4705a291cdb4a2fd352f013134f26c6ff63477f20235138d1d21009"}, 856 | {file = "msgpack-1.0.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2a5866bdc88d77f6e1370f82f2371c9bc6fc92fe898fa2dec0c5d4f5435a2694"}, 857 | {file = "msgpack-1.0.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:92be4b12de4806d3c36810b0fe2aeedd8d493db39e2eb90742b9c09299eb5759"}, 858 | {file = "msgpack-1.0.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:de6bd7990a2c2dabe926b7e62a92886ccbf809425c347ae7de277067f97c2887"}, 859 | {file = "msgpack-1.0.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5a9ee2540c78659a1dd0b110f73773533ee3108d4e1219b5a15a8d635b7aca0e"}, 860 | {file = "msgpack-1.0.2-cp39-cp39-win32.whl", hash = "sha256:c747c0cc08bd6d72a586310bda6ea72eeb28e7505990f342552315b229a19b33"}, 861 | {file = "msgpack-1.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:d8167b84af26654c1124857d71650404336f4eb5cc06900667a493fc619ddd9f"}, 862 | {file = "msgpack-1.0.2.tar.gz", hash = "sha256:fae04496f5bc150eefad4e9571d1a76c55d021325dcd484ce45065ebbdd00984"}, 863 | ] 864 | mypy-extensions = [ 865 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 866 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 867 | ] 868 | pastel = [ 869 | {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"}, 870 | {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, 871 | ] 872 | pathspec = [ 873 | {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, 874 | {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, 875 | ] 876 | poethepoet = [ 877 | {file = "poethepoet-0.10.0-py3-none-any.whl", hash = "sha256:6fb3021603d4421c6fcc40072bbcf150a6c52ef70ff4d3be089b8b04e015ef5a"}, 878 | {file = "poethepoet-0.10.0.tar.gz", hash = "sha256:70b97cb194b978dc464c70793e85e6f746cddf82b84a38bfb135946ad71ae19c"}, 879 | ] 880 | pyasn1 = [ 881 | {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, 882 | {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, 883 | {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, 884 | {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, 885 | {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, 886 | {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, 887 | {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, 888 | {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, 889 | {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, 890 | {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, 891 | {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, 892 | {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, 893 | {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, 894 | ] 895 | pyasn1-modules = [ 896 | {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"}, 897 | {file = "pyasn1_modules-0.2.8-py2.4.egg", hash = "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199"}, 898 | {file = "pyasn1_modules-0.2.8-py2.5.egg", hash = "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"}, 899 | {file = "pyasn1_modules-0.2.8-py2.6.egg", hash = "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb"}, 900 | {file = "pyasn1_modules-0.2.8-py2.7.egg", hash = "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8"}, 901 | {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"}, 902 | {file = "pyasn1_modules-0.2.8-py3.1.egg", hash = "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d"}, 903 | {file = "pyasn1_modules-0.2.8-py3.2.egg", hash = "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45"}, 904 | {file = "pyasn1_modules-0.2.8-py3.3.egg", hash = "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4"}, 905 | {file = "pyasn1_modules-0.2.8-py3.4.egg", hash = "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811"}, 906 | {file = "pyasn1_modules-0.2.8-py3.5.egg", hash = "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed"}, 907 | {file = "pyasn1_modules-0.2.8-py3.6.egg", hash = "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0"}, 908 | {file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"}, 909 | ] 910 | pycparser = [ 911 | {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, 912 | {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, 913 | ] 914 | pyopenssl = [ 915 | {file = "pyOpenSSL-20.0.1-py2.py3-none-any.whl", hash = "sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b"}, 916 | {file = "pyOpenSSL-20.0.1.tar.gz", hash = "sha256:4c231c759543ba02560fcd2480c48dcec4dae34c9da7d3747c508227e0624b51"}, 917 | ] 918 | pytz = [ 919 | {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, 920 | {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, 921 | ] 922 | regex = [ 923 | {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, 924 | {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, 925 | {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, 926 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, 927 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, 928 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, 929 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, 930 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, 931 | {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, 932 | {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, 933 | {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, 934 | {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, 935 | {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, 936 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, 937 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, 938 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, 939 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, 940 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, 941 | {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, 942 | {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, 943 | {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, 944 | {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, 945 | {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, 946 | {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, 947 | {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, 948 | {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, 949 | {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, 950 | {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, 951 | {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, 952 | {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, 953 | {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, 954 | {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, 955 | {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, 956 | {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, 957 | {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, 958 | {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, 959 | {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, 960 | {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, 961 | {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, 962 | {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, 963 | {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, 964 | ] 965 | service-identity = [ 966 | {file = "service-identity-21.1.0.tar.gz", hash = "sha256:6e6c6086ca271dc11b033d17c3a8bea9f24ebff920c587da090afc9519419d34"}, 967 | {file = "service_identity-21.1.0-py2.py3-none-any.whl", hash = "sha256:f0b0caac3d40627c3c04d7a51b6e06721857a0e10a8775f2d1d7e72901b3a7db"}, 968 | ] 969 | six = [ 970 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 971 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 972 | ] 973 | soupsieve = [ 974 | {file = "soupsieve-2.2.1-py3-none-any.whl", hash = "sha256:c2c1c2d44f158cdbddab7824a9af8c4f83c76b1e23e049479aa432feb6c4c23b"}, 975 | {file = "soupsieve-2.2.1.tar.gz", hash = "sha256:052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc"}, 976 | ] 977 | sqlparse = [ 978 | {file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"}, 979 | {file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"}, 980 | ] 981 | toml = [ 982 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 983 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 984 | ] 985 | tomlkit = [ 986 | {file = "tomlkit-0.7.0-py2.py3-none-any.whl", hash = "sha256:6babbd33b17d5c9691896b0e68159215a9387ebfa938aa3ac42f4a4beeb2b831"}, 987 | {file = "tomlkit-0.7.0.tar.gz", hash = "sha256:ac57f29693fab3e309ea789252fcce3061e19110085aa31af5446ca749325618"}, 988 | ] 989 | twisted = [ 990 | {file = "Twisted-21.2.0-py3-none-any.whl", hash = "sha256:aab38085ea6cda5b378b519a0ec99986874921ee8881318626b0a3414bb2631e"}, 991 | {file = "Twisted-21.2.0.tar.gz", hash = "sha256:77544a8945cf69b98d2946689bbe0c75de7d145cdf11f391dd487eae8fc95a12"}, 992 | ] 993 | twisted-iocpsupport = [ 994 | {file = "twisted-iocpsupport-1.0.1.tar.gz", hash = "sha256:bdda01692988f29d43d832a4b1f402199d0c7d80083ad65059a0892a3670fc9a"}, 995 | {file = "twisted_iocpsupport-1.0.1-cp27-cp27m-win32.whl", hash = "sha256:34a3dfd743f9b7a464ba72c9fa9fc21e2a5e5d2f5a8c805f3e3057a2b3c1f7de"}, 996 | {file = "twisted_iocpsupport-1.0.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e9cfab3e9dbc3a541722b7e34656e3694a7a09d113c40b51213e8d8a5b6a2314"}, 997 | {file = "twisted_iocpsupport-1.0.1-cp35-cp35m-win32.whl", hash = "sha256:28770b4d0da9364df81371e962323a8390b181eeeccec91eb21316489a4ee337"}, 998 | {file = "twisted_iocpsupport-1.0.1-cp35-cp35m-win_amd64.whl", hash = "sha256:95595fb8d242b802b21f2c0652af0997287dcba67f4f85285be9adc15572e462"}, 999 | {file = "twisted_iocpsupport-1.0.1-cp36-cp36m-win32.whl", hash = "sha256:de5a2273df46fafab430fadfe0213943ffbc0cdb8ab5d49cb41e0610043ee70e"}, 1000 | {file = "twisted_iocpsupport-1.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f3c505de2237ed47c20e425dfc903a2ee1ee2cf08654f2c88898e50a60a5f210"}, 1001 | {file = "twisted_iocpsupport-1.0.1-cp37-cp37m-win32.whl", hash = "sha256:90e6f15acf86de2b66e1441fb1c4bcbba4e1361b799fa5c20899e453eb66c5c9"}, 1002 | {file = "twisted_iocpsupport-1.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4b9fd9b059545536d5f7ac10f642ebba0437203e884479eb9d01d636dd287414"}, 1003 | {file = "twisted_iocpsupport-1.0.1-cp38-cp38-win32.whl", hash = "sha256:e1dae2dd44a11153b169dda012b222bce524792dc64859ab2b86deb7a0915863"}, 1004 | {file = "twisted_iocpsupport-1.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f5c45094f16c1c0749453a9341bfe818cb5f4543e76ae744bc983c992a0abdd"}, 1005 | {file = "twisted_iocpsupport-1.0.1-cp39-cp39-win32.whl", hash = "sha256:5b9ce8803023d0818ab2507fe222c16c193bae091abb59a45841a757ffe198fe"}, 1006 | {file = "twisted_iocpsupport-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8dab94de3a9bafae1e3ec92aec01f1239b2b4b4f3866162f2c5f2e649e2661dc"}, 1007 | {file = "twisted_iocpsupport-1.0.1-pp27-pypy_73-win32.whl", hash = "sha256:eb033bed4f20d2053767054c33be4d8ae09faf23d547681ecd5ff463d97bc099"}, 1008 | {file = "twisted_iocpsupport-1.0.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:ed8cb959d7dce287419e766267a280139fedf14217d85695d7b6384d8ccd3353"}, 1009 | {file = "twisted_iocpsupport-1.0.1-pp37-pypy37_pp73-win32.whl", hash = "sha256:1535e537b6d60d0cfd2b4785f1c7a021beb62d8df6bac3dc6c45bb866ce648e4"}, 1010 | ] 1011 | txaio = [ 1012 | {file = "txaio-21.2.1-py2.py3-none-any.whl", hash = "sha256:c16b55f9a67b2419cfdf8846576e2ec9ba94fe6978a83080c352a80db31c93fb"}, 1013 | {file = "txaio-21.2.1.tar.gz", hash = "sha256:7d6f89745680233f1c4db9ddb748df5e88d2a7a37962be174c0fd04c8dba1dc8"}, 1014 | ] 1015 | typed-ast = [ 1016 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, 1017 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, 1018 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, 1019 | {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, 1020 | {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, 1021 | {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, 1022 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, 1023 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, 1024 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, 1025 | {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, 1026 | {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, 1027 | {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, 1028 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, 1029 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, 1030 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, 1031 | {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, 1032 | {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, 1033 | {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, 1034 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, 1035 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, 1036 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, 1037 | {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, 1038 | {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, 1039 | {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, 1040 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, 1041 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, 1042 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, 1043 | {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, 1044 | {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, 1045 | {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, 1046 | ] 1047 | typing-extensions = [ 1048 | {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, 1049 | {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, 1050 | {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, 1051 | ] 1052 | "zope.interface" = [ 1053 | {file = "zope.interface-5.4.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7"}, 1054 | {file = "zope.interface-5.4.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021"}, 1055 | {file = "zope.interface-5.4.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192"}, 1056 | {file = "zope.interface-5.4.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a"}, 1057 | {file = "zope.interface-5.4.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531"}, 1058 | {file = "zope.interface-5.4.0-cp27-cp27m-win32.whl", hash = "sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325"}, 1059 | {file = "zope.interface-5.4.0-cp27-cp27m-win_amd64.whl", hash = "sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155"}, 1060 | {file = "zope.interface-5.4.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263"}, 1061 | {file = "zope.interface-5.4.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959"}, 1062 | {file = "zope.interface-5.4.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e"}, 1063 | {file = "zope.interface-5.4.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c"}, 1064 | {file = "zope.interface-5.4.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702"}, 1065 | {file = "zope.interface-5.4.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f"}, 1066 | {file = "zope.interface-5.4.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05"}, 1067 | {file = "zope.interface-5.4.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004"}, 1068 | {file = "zope.interface-5.4.0-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117"}, 1069 | {file = "zope.interface-5.4.0-cp35-cp35m-win32.whl", hash = "sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8"}, 1070 | {file = "zope.interface-5.4.0-cp35-cp35m-win_amd64.whl", hash = "sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63"}, 1071 | {file = "zope.interface-5.4.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f"}, 1072 | {file = "zope.interface-5.4.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920"}, 1073 | {file = "zope.interface-5.4.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46"}, 1074 | {file = "zope.interface-5.4.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc"}, 1075 | {file = "zope.interface-5.4.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9"}, 1076 | {file = "zope.interface-5.4.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2"}, 1077 | {file = "zope.interface-5.4.0-cp36-cp36m-win32.whl", hash = "sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78"}, 1078 | {file = "zope.interface-5.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1"}, 1079 | {file = "zope.interface-5.4.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e"}, 1080 | {file = "zope.interface-5.4.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b"}, 1081 | {file = "zope.interface-5.4.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f"}, 1082 | {file = "zope.interface-5.4.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d"}, 1083 | {file = "zope.interface-5.4.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8"}, 1084 | {file = "zope.interface-5.4.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf"}, 1085 | {file = "zope.interface-5.4.0-cp37-cp37m-win32.whl", hash = "sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7"}, 1086 | {file = "zope.interface-5.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94"}, 1087 | {file = "zope.interface-5.4.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3"}, 1088 | {file = "zope.interface-5.4.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e"}, 1089 | {file = "zope.interface-5.4.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7"}, 1090 | {file = "zope.interface-5.4.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120"}, 1091 | {file = "zope.interface-5.4.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48"}, 1092 | {file = "zope.interface-5.4.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4"}, 1093 | {file = "zope.interface-5.4.0-cp38-cp38-win32.whl", hash = "sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb"}, 1094 | {file = "zope.interface-5.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54"}, 1095 | {file = "zope.interface-5.4.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4"}, 1096 | {file = "zope.interface-5.4.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d"}, 1097 | {file = "zope.interface-5.4.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83"}, 1098 | {file = "zope.interface-5.4.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25"}, 1099 | {file = "zope.interface-5.4.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1"}, 1100 | {file = "zope.interface-5.4.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c"}, 1101 | {file = "zope.interface-5.4.0-cp39-cp39-win32.whl", hash = "sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e"}, 1102 | {file = "zope.interface-5.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09"}, 1103 | {file = "zope.interface-5.4.0.tar.gz", hash = "sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e"}, 1104 | ] 1105 | -------------------------------------------------------------------------------- /sockpuppet/project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/djangocon-eu-2021-conference-talk/33db4b72a25ed435c94bc75eaba24b5153ae4970/sockpuppet/project/__init__.py -------------------------------------------------------------------------------- /sockpuppet/project/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for project project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /sockpuppet/project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for project project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.2.3. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "django-insecure-op-5by_fw5-3auu(8st)xojjj-9gcbdzx$w5*q!58$il#36ktb" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | "django.contrib.admin", 35 | "django.contrib.auth", 36 | "django.contrib.contenttypes", 37 | "django.contrib.sessions", 38 | "django.contrib.messages", 39 | "django.contrib.staticfiles", 40 | "channels", # required for websockets 41 | "sockpuppet", # required for sockpuppet 42 | "todo", 43 | ] 44 | 45 | MIDDLEWARE = [ 46 | "django.middleware.security.SecurityMiddleware", 47 | "django.contrib.sessions.middleware.SessionMiddleware", 48 | "django.middleware.common.CommonMiddleware", 49 | "django.middleware.csrf.CsrfViewMiddleware", 50 | "django.contrib.auth.middleware.AuthenticationMiddleware", 51 | "django.contrib.messages.middleware.MessageMiddleware", 52 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 53 | ] 54 | 55 | ROOT_URLCONF = "project.urls" 56 | 57 | TEMPLATES = [ 58 | { 59 | "BACKEND": "django.template.backends.django.DjangoTemplates", 60 | "DIRS": [], 61 | "APP_DIRS": True, 62 | "OPTIONS": { 63 | "context_processors": [ 64 | "django.template.context_processors.debug", 65 | "django.template.context_processors.request", 66 | "django.contrib.auth.context_processors.auth", 67 | "django.contrib.messages.context_processors.messages", 68 | ], 69 | }, 70 | }, 71 | ] 72 | 73 | # WSGI_APPLICATION = "project.wsgi.application" 74 | 75 | 76 | # Database 77 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases 78 | 79 | DATABASES = { 80 | "default": { 81 | "ENGINE": "django.db.backends.sqlite3", 82 | "NAME": BASE_DIR / "db.sqlite3", 83 | } 84 | } 85 | 86 | 87 | # Password validation 88 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 89 | 90 | AUTH_PASSWORD_VALIDATORS = [ 91 | { 92 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 93 | }, 94 | { 95 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 96 | }, 97 | { 98 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 99 | }, 100 | { 101 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 102 | }, 103 | ] 104 | 105 | 106 | # Internationalization 107 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 108 | 109 | LANGUAGE_CODE = "en-us" 110 | 111 | TIME_ZONE = "UTC" 112 | 113 | USE_I18N = True 114 | 115 | USE_L10N = True 116 | 117 | USE_TZ = True 118 | 119 | 120 | # Static files (CSS, JavaScript, Images) 121 | # https://docs.djangoproject.com/en/3.2/howto/static-files/ 122 | 123 | STATIC_URL = "/static/" 124 | 125 | # Default primary key field type 126 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 127 | 128 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 129 | 130 | 131 | # Required for sockpuppet 132 | 133 | ASGI_APPLICATION = "sockpuppet.routing.application" 134 | 135 | CHANNEL_LAYERS = { 136 | "default": {"BACKEND": "channels.layers.InMemoryChannelLayer"}, 137 | } 138 | -------------------------------------------------------------------------------- /sockpuppet/project/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from todo.views.todo import TodoView 4 | 5 | 6 | urlpatterns = [ 7 | path("", TodoView.as_view()), 8 | ] 9 | -------------------------------------------------------------------------------- /sockpuppet/project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /sockpuppet/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "sockpuppet" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Adam Hill "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.7" 10 | Django = "^3.2.3" 11 | django-sockpuppet = {extras = ["lxml"], version = "^0.6.0"} 12 | 13 | [tool.poetry.dev-dependencies] 14 | poethepoet = "^0.10.0" 15 | black = "^21.5b1" 16 | 17 | [tool.poe.tasks] 18 | m = "./manage.py" 19 | r = "./manage.py runserver 0:8002" 20 | ma = "./manage.py makemigrations" 21 | mi = "./manage.py migrate" 22 | md = ["ma", "mi"] 23 | 24 | [build-system] 25 | requires = ["poetry-core>=1.0.0"] 26 | build-backend = "poetry.core.masonry.api" 27 | -------------------------------------------------------------------------------- /sockpuppet/todo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/djangocon-eu-2021-conference-talk/33db4b72a25ed435c94bc75eaba24b5153ae4970/sockpuppet/todo/__init__.py -------------------------------------------------------------------------------- /sockpuppet/todo/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TodoConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "todo" 7 | -------------------------------------------------------------------------------- /sockpuppet/todo/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.3 on 2021-05-15 19:38 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="Todo", 15 | fields=[ 16 | ( 17 | "id", 18 | models.BigAutoField( 19 | auto_created=True, 20 | primary_key=True, 21 | serialize=False, 22 | verbose_name="ID", 23 | ), 24 | ), 25 | ("task", models.CharField(max_length=1024)), 26 | ], 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /sockpuppet/todo/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/djangocon-eu-2021-conference-talk/33db4b72a25ed435c94bc75eaba24b5153ae4970/sockpuppet/todo/migrations/__init__.py -------------------------------------------------------------------------------- /sockpuppet/todo/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Todo(models.Model): 5 | task = models.CharField(max_length=1024) 6 | -------------------------------------------------------------------------------- /sockpuppet/todo/reflexes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/djangocon-eu-2021-conference-talk/33db4b72a25ed435c94bc75eaba24b5153ae4970/sockpuppet/todo/reflexes/__init__.py -------------------------------------------------------------------------------- /sockpuppet/todo/reflexes/todo_reflex.py: -------------------------------------------------------------------------------- 1 | from sockpuppet.reflex import Reflex 2 | 3 | from todo.models import Todo 4 | 5 | 6 | class TodoReflex(Reflex): 7 | def new_task(self): 8 | task = self.element.attributes["value"] 9 | self.request.session["new_task"] = task 10 | 11 | def add_todo(self): 12 | task = self.request.session["new_task"] 13 | todo = Todo(task=task) 14 | todo.save() 15 | 16 | def cancel(self): 17 | self.request.session["new_task"] = "" 18 | -------------------------------------------------------------------------------- /sockpuppet/todo/templates/todo.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | Sockpuppet 8 | 9 | 13 | 14 | 15 | 16 |
17 |
18 |

Sockpuppet

19 | 20 |
21 |
22 | 23 |
24 | 25 |
26 | 32 |
33 |
34 | 35 |
36 |
37 | 43 |
44 |
45 | 51 |
52 |
53 |
54 | 55 | 56 | 57 |
58 |

59 | Current Tasks 60 |

61 | 62 | {% if not todos %} 63 |

None, yet!

64 | {% else %} 65 |
    66 | {% for todo in todos %} 67 |
  • {{ todo.task }}
  • 68 | {% endfor %} 69 |
70 | {% endif %} 71 |
72 | 73 |
74 |
75 |
76 | 77 | 78 | -------------------------------------------------------------------------------- /sockpuppet/todo/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/djangocon-eu-2021-conference-talk/33db4b72a25ed435c94bc75eaba24b5153ae4970/sockpuppet/todo/views/__init__.py -------------------------------------------------------------------------------- /sockpuppet/todo/views/todo.py: -------------------------------------------------------------------------------- 1 | from django.views.generic.base import TemplateView 2 | 3 | from todo.models import Todo 4 | 5 | 6 | class TodoView(TemplateView): 7 | template_name = "todo.html" 8 | 9 | def get_context_data(self, *args, **kwargs): 10 | context = super().get_context_data(*args, **kwargs) 11 | context["todos"] = Todo.objects.all() 12 | 13 | return context 14 | -------------------------------------------------------------------------------- /sockpuppet/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const glob = require('glob'); 3 | 4 | 5 | let globOptions = { 6 | ignore: ['node_modules/**', 'venv/**'] 7 | } 8 | 9 | let entryFiles = glob.sync("**/javascript/*.js", globOptions) 10 | 11 | let entryObj = {}; 12 | entryFiles.forEach(function(file){ 13 | if (file.includes('.')) { 14 | let parts = file.split('/') 15 | let path = parts.pop() 16 | let fileName = path.split('.')[0]; 17 | entryObj[fileName] = `./${file}`; 18 | } 19 | }); 20 | 21 | const config = { 22 | mode: process.env.NODE_ENV, 23 | entry: entryObj, 24 | output: { 25 | path: __dirname + '/dist/js', 26 | filename: '[name].js' 27 | }, 28 | optimization: { 29 | minimize: false 30 | } 31 | } 32 | 33 | module.exports = config 34 | -------------------------------------------------------------------------------- /unicorn/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /unicorn/poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "appdirs" 3 | version = "1.4.4" 4 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 5 | category = "dev" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [[package]] 10 | name = "asgiref" 11 | version = "3.3.4" 12 | description = "ASGI specs, helper code, and adapters" 13 | category = "main" 14 | optional = false 15 | python-versions = ">=3.6" 16 | 17 | [package.dependencies] 18 | typing-extensions = {version = "*", markers = "python_version < \"3.8\""} 19 | 20 | [package.extras] 21 | tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] 22 | 23 | [[package]] 24 | name = "beautifulsoup4" 25 | version = "4.9.3" 26 | description = "Screen-scraping library" 27 | category = "main" 28 | optional = false 29 | python-versions = "*" 30 | 31 | [package.dependencies] 32 | soupsieve = {version = ">1.2", markers = "python_version >= \"3.0\""} 33 | 34 | [package.extras] 35 | html5lib = ["html5lib"] 36 | lxml = ["lxml"] 37 | 38 | [[package]] 39 | name = "black" 40 | version = "21.5b1" 41 | description = "The uncompromising code formatter." 42 | category = "dev" 43 | optional = false 44 | python-versions = ">=3.6.2" 45 | 46 | [package.dependencies] 47 | appdirs = "*" 48 | click = ">=7.1.2" 49 | mypy-extensions = ">=0.4.3" 50 | pathspec = ">=0.8.1,<1" 51 | regex = ">=2020.1.8" 52 | toml = ">=0.10.1" 53 | typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""} 54 | typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} 55 | 56 | [package.extras] 57 | colorama = ["colorama (>=0.4.3)"] 58 | d = ["aiohttp (>=3.6.0)", "aiohttp-cors"] 59 | python2 = ["typed-ast (>=1.4.2)"] 60 | 61 | [[package]] 62 | name = "cachetools" 63 | version = "4.2.2" 64 | description = "Extensible memoizing collections and decorators" 65 | category = "main" 66 | optional = false 67 | python-versions = "~=3.5" 68 | 69 | [[package]] 70 | name = "click" 71 | version = "8.0.0" 72 | description = "Composable command line interface toolkit" 73 | category = "dev" 74 | optional = false 75 | python-versions = ">=3.6" 76 | 77 | [package.dependencies] 78 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 79 | 80 | [[package]] 81 | name = "colorama" 82 | version = "0.4.4" 83 | description = "Cross-platform colored terminal text." 84 | category = "dev" 85 | optional = false 86 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 87 | 88 | [[package]] 89 | name = "decorator" 90 | version = "4.4.2" 91 | description = "Decorators for Humans" 92 | category = "main" 93 | optional = false 94 | python-versions = ">=2.6, !=3.0.*, !=3.1.*" 95 | 96 | [[package]] 97 | name = "django" 98 | version = "3.2.3" 99 | description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." 100 | category = "main" 101 | optional = false 102 | python-versions = ">=3.6" 103 | 104 | [package.dependencies] 105 | asgiref = ">=3.3.2,<4" 106 | pytz = "*" 107 | sqlparse = ">=0.2.2" 108 | 109 | [package.extras] 110 | argon2 = ["argon2-cffi (>=19.1.0)"] 111 | bcrypt = ["bcrypt"] 112 | 113 | [[package]] 114 | name = "django-unicorn" 115 | version = "0.27.2" 116 | description = "A magical full-stack framework for Django." 117 | category = "main" 118 | optional = false 119 | python-versions = ">=3.6,<4.0" 120 | 121 | [package.dependencies] 122 | beautifulsoup4 = ">=4.8.0" 123 | cachetools = ">=4.1.1,<5.0.0" 124 | decorator = ">=4.4.2,<5.0.0" 125 | django = ">=2.2" 126 | orjson = ">=3.2.1,<4.0.0" 127 | shortuuid = ">=1.0.1,<2.0.0" 128 | 129 | [[package]] 130 | name = "mypy-extensions" 131 | version = "0.4.3" 132 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 133 | category = "dev" 134 | optional = false 135 | python-versions = "*" 136 | 137 | [[package]] 138 | name = "orjson" 139 | version = "3.5.2" 140 | description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" 141 | category = "main" 142 | optional = false 143 | python-versions = ">=3.6" 144 | 145 | [[package]] 146 | name = "pastel" 147 | version = "0.2.1" 148 | description = "Bring colors to your terminal." 149 | category = "dev" 150 | optional = false 151 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 152 | 153 | [[package]] 154 | name = "pathspec" 155 | version = "0.8.1" 156 | description = "Utility library for gitignore style pattern matching of file paths." 157 | category = "dev" 158 | optional = false 159 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 160 | 161 | [[package]] 162 | name = "poethepoet" 163 | version = "0.10.0" 164 | description = "A task runner that works well with poetry." 165 | category = "dev" 166 | optional = false 167 | python-versions = ">=3.6,<4.0" 168 | 169 | [package.dependencies] 170 | pastel = ">=0.2.0,<0.3.0" 171 | tomlkit = ">=0.6.0,<1.0.0" 172 | 173 | [[package]] 174 | name = "pytz" 175 | version = "2021.1" 176 | description = "World timezone definitions, modern and historical" 177 | category = "main" 178 | optional = false 179 | python-versions = "*" 180 | 181 | [[package]] 182 | name = "regex" 183 | version = "2021.4.4" 184 | description = "Alternative regular expression module, to replace re." 185 | category = "dev" 186 | optional = false 187 | python-versions = "*" 188 | 189 | [[package]] 190 | name = "shortuuid" 191 | version = "1.0.1" 192 | description = "A generator library for concise, unambiguous and URL-safe UUIDs." 193 | category = "main" 194 | optional = false 195 | python-versions = ">=3.5" 196 | 197 | [[package]] 198 | name = "soupsieve" 199 | version = "2.2.1" 200 | description = "A modern CSS selector implementation for Beautiful Soup." 201 | category = "main" 202 | optional = false 203 | python-versions = ">=3.6" 204 | 205 | [[package]] 206 | name = "sqlparse" 207 | version = "0.4.1" 208 | description = "A non-validating SQL parser." 209 | category = "main" 210 | optional = false 211 | python-versions = ">=3.5" 212 | 213 | [[package]] 214 | name = "toml" 215 | version = "0.10.2" 216 | description = "Python Library for Tom's Obvious, Minimal Language" 217 | category = "dev" 218 | optional = false 219 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 220 | 221 | [[package]] 222 | name = "tomlkit" 223 | version = "0.7.0" 224 | description = "Style preserving TOML library" 225 | category = "dev" 226 | optional = false 227 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 228 | 229 | [[package]] 230 | name = "typed-ast" 231 | version = "1.4.3" 232 | description = "a fork of Python 2 and 3 ast modules with type comment support" 233 | category = "dev" 234 | optional = false 235 | python-versions = "*" 236 | 237 | [[package]] 238 | name = "typing-extensions" 239 | version = "3.10.0.0" 240 | description = "Backported and Experimental Type Hints for Python 3.5+" 241 | category = "main" 242 | optional = false 243 | python-versions = "*" 244 | 245 | [metadata] 246 | lock-version = "1.1" 247 | python-versions = "^3.7" 248 | content-hash = "9408858004a87c1129db5c1f80eddfa9616e9ea38971e057bae7d3cbdca225f9" 249 | 250 | [metadata.files] 251 | appdirs = [ 252 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 253 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 254 | ] 255 | asgiref = [ 256 | {file = "asgiref-3.3.4-py3-none-any.whl", hash = "sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee"}, 257 | {file = "asgiref-3.3.4.tar.gz", hash = "sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78"}, 258 | ] 259 | beautifulsoup4 = [ 260 | {file = "beautifulsoup4-4.9.3-py2-none-any.whl", hash = "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35"}, 261 | {file = "beautifulsoup4-4.9.3-py3-none-any.whl", hash = "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"}, 262 | {file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"}, 263 | ] 264 | black = [ 265 | {file = "black-21.5b1-py3-none-any.whl", hash = "sha256:8a60071a0043876a4ae96e6c69bd3a127dad2c1ca7c8083573eb82f92705d008"}, 266 | {file = "black-21.5b1.tar.gz", hash = "sha256:23695358dbcb3deafe7f0a3ad89feee5999a46be5fec21f4f1d108be0bcdb3b1"}, 267 | ] 268 | cachetools = [ 269 | {file = "cachetools-4.2.2-py3-none-any.whl", hash = "sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001"}, 270 | {file = "cachetools-4.2.2.tar.gz", hash = "sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff"}, 271 | ] 272 | click = [ 273 | {file = "click-8.0.0-py3-none-any.whl", hash = "sha256:e90e62ced43dc8105fb9a26d62f0d9340b5c8db053a814e25d95c19873ae87db"}, 274 | {file = "click-8.0.0.tar.gz", hash = "sha256:7d8c289ee437bcb0316820ccee14aefcb056e58d31830ecab8e47eda6540e136"}, 275 | ] 276 | colorama = [ 277 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 278 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 279 | ] 280 | decorator = [ 281 | {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, 282 | {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, 283 | ] 284 | django = [ 285 | {file = "Django-3.2.3-py3-none-any.whl", hash = "sha256:7e0a1393d18c16b503663752a8b6790880c5084412618990ce8a81cc908b4962"}, 286 | {file = "Django-3.2.3.tar.gz", hash = "sha256:13ac78dbfd189532cad8f383a27e58e18b3d33f80009ceb476d7fcbfc5dcebd8"}, 287 | ] 288 | django-unicorn = [ 289 | {file = "django-unicorn-0.27.2.tar.gz", hash = "sha256:0b0ba4a1dcb689e0968f2688194f92692542b93ad6f2f73a450f22efd0d22965"}, 290 | {file = "django_unicorn-0.27.2-py3-none-any.whl", hash = "sha256:fd33ab6cf80490ca11daa8f232df717cabb413eaba20bf3ad393f634562da6b0"}, 291 | ] 292 | mypy-extensions = [ 293 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 294 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 295 | ] 296 | orjson = [ 297 | {file = "orjson-3.5.2-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:2ba4165883fbef0985bce60bddbf91bc5cea77cc22b1c12fe7a716c6323ab1e7"}, 298 | {file = "orjson-3.5.2-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:cee746d186ba9efa47b9d52a649ee0617456a9a4d7a2cbd3ec06330bb9cb372a"}, 299 | {file = "orjson-3.5.2-cp36-cp36m-macosx_10_7_x86_64.whl", hash = "sha256:8591a25a31a89cf2a33e30eb516ab028bad2c72fed04e323917114aaedc07c7d"}, 300 | {file = "orjson-3.5.2-cp36-cp36m-macosx_10_9_universal2.whl", hash = "sha256:38cb8cdbf43eafc6dcbfb10a9e63c80727bb916aee0f75caf5f90e5355b266e1"}, 301 | {file = "orjson-3.5.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:96b403796fc7e44bae843a2a83923925fe048f3a67c10a298fdfc0ff46163c14"}, 302 | {file = "orjson-3.5.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:5b66a62d4c0c44441b23fafcd3d0892296d9793361b14bcc5a5645c88b6a4a71"}, 303 | {file = "orjson-3.5.2-cp36-none-win_amd64.whl", hash = "sha256:609e93919268fadb871aafb7f550c3fe8d3e8c1305cadcc1610b414113b7034e"}, 304 | {file = "orjson-3.5.2-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:200bd4491052d13696456a92d23f086b68b526c2464248733964e8165ac60888"}, 305 | {file = "orjson-3.5.2-cp37-cp37m-macosx_10_9_universal2.whl", hash = "sha256:cc614bf6bfe0181e51dd98a9c53669f08d4d8641efbf1a287113da3059773dea"}, 306 | {file = "orjson-3.5.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:43576bed3be300e9c02629a8d5fb3340fe6474765e6eee9610067def4b3ac19c"}, 307 | {file = "orjson-3.5.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:acd735718b531b78858a7e932c58424c5a3e39e04d61bba3d95ce8a8498ea9e9"}, 308 | {file = "orjson-3.5.2-cp37-none-win_amd64.whl", hash = "sha256:7503145ffd1ae90d487860b97e2867ec61c2c8f001209bb12700ba7833df8ddf"}, 309 | {file = "orjson-3.5.2-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:9c37cf3dbc9c81abed04ba4854454e9f0d8ac7c05fb6c4f36545733e90be6af2"}, 310 | {file = "orjson-3.5.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e6ef00ddc637b7d13926aaccdabac363efdfd348c132410eb054c27e2eae6a7"}, 311 | {file = "orjson-3.5.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:9d0834ca40c6e467fa1f1db3f83a8c3562c03eb2b7067ad09de5019592edb88f"}, 312 | {file = "orjson-3.5.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:d4a2ddc6342a8280dafaa69827b387b95856ef0a6c5812fe91f5bd21ddd2ef36"}, 313 | {file = "orjson-3.5.2-cp38-none-win_amd64.whl", hash = "sha256:f54f8bcf24812a524e8904a80a365f7a287d82fc6ebdee528149616070abe5ab"}, 314 | {file = "orjson-3.5.2-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:8b429471398ea37d848fb53bca6a8c42fb776c278f4fcb6a1d651b8f1fb64947"}, 315 | {file = "orjson-3.5.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13fd458110fbe019c2a67ee539678189444f73bc09b27983c9b42663c63e0445"}, 316 | {file = "orjson-3.5.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8bf1145a06e1245f0c8a8c32df6ffe52d214eb4eb88c3fb32e4ed14e3dc38e0e"}, 317 | {file = "orjson-3.5.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:7e3434010e3f0680e92bb0a6094e4d5c939d0c4258c76397c6bd5263c7d62e86"}, 318 | {file = "orjson-3.5.2-cp39-none-win_amd64.whl", hash = "sha256:df9730cc8cd22b3f54aa55317257f3279e6300157fc0f4ed4424586cd7eb012d"}, 319 | {file = "orjson-3.5.2.tar.gz", hash = "sha256:f385253a6ddac37ea422ec2c0d35772b4f5bf0dc0803ce44543bf7e530423ef8"}, 320 | ] 321 | pastel = [ 322 | {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"}, 323 | {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, 324 | ] 325 | pathspec = [ 326 | {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, 327 | {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, 328 | ] 329 | poethepoet = [ 330 | {file = "poethepoet-0.10.0-py3-none-any.whl", hash = "sha256:6fb3021603d4421c6fcc40072bbcf150a6c52ef70ff4d3be089b8b04e015ef5a"}, 331 | {file = "poethepoet-0.10.0.tar.gz", hash = "sha256:70b97cb194b978dc464c70793e85e6f746cddf82b84a38bfb135946ad71ae19c"}, 332 | ] 333 | pytz = [ 334 | {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, 335 | {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, 336 | ] 337 | regex = [ 338 | {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, 339 | {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, 340 | {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, 341 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, 342 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, 343 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, 344 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, 345 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, 346 | {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, 347 | {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, 348 | {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, 349 | {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, 350 | {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, 351 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, 352 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, 353 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, 354 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, 355 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, 356 | {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, 357 | {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, 358 | {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, 359 | {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, 360 | {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, 361 | {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, 362 | {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, 363 | {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, 364 | {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, 365 | {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, 366 | {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, 367 | {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, 368 | {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, 369 | {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, 370 | {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, 371 | {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, 372 | {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, 373 | {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, 374 | {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, 375 | {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, 376 | {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, 377 | {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, 378 | {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, 379 | ] 380 | shortuuid = [ 381 | {file = "shortuuid-1.0.1-py3-none-any.whl", hash = "sha256:492c7402ff91beb1342a5898bd61ea953985bf24a41cd9f247409aa2e03c8f77"}, 382 | {file = "shortuuid-1.0.1.tar.gz", hash = "sha256:3c11d2007b915c43bee3e10625f068d8a349e04f0d81f08f5fa08507427ebf1f"}, 383 | ] 384 | soupsieve = [ 385 | {file = "soupsieve-2.2.1-py3-none-any.whl", hash = "sha256:c2c1c2d44f158cdbddab7824a9af8c4f83c76b1e23e049479aa432feb6c4c23b"}, 386 | {file = "soupsieve-2.2.1.tar.gz", hash = "sha256:052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc"}, 387 | ] 388 | sqlparse = [ 389 | {file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"}, 390 | {file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"}, 391 | ] 392 | toml = [ 393 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 394 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 395 | ] 396 | tomlkit = [ 397 | {file = "tomlkit-0.7.0-py2.py3-none-any.whl", hash = "sha256:6babbd33b17d5c9691896b0e68159215a9387ebfa938aa3ac42f4a4beeb2b831"}, 398 | {file = "tomlkit-0.7.0.tar.gz", hash = "sha256:ac57f29693fab3e309ea789252fcce3061e19110085aa31af5446ca749325618"}, 399 | ] 400 | typed-ast = [ 401 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, 402 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, 403 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, 404 | {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, 405 | {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, 406 | {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, 407 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, 408 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, 409 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, 410 | {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, 411 | {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, 412 | {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, 413 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, 414 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, 415 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, 416 | {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, 417 | {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, 418 | {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, 419 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, 420 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, 421 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, 422 | {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, 423 | {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, 424 | {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, 425 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, 426 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, 427 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, 428 | {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, 429 | {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, 430 | {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, 431 | ] 432 | typing-extensions = [ 433 | {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, 434 | {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, 435 | {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, 436 | ] 437 | -------------------------------------------------------------------------------- /unicorn/project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/djangocon-eu-2021-conference-talk/33db4b72a25ed435c94bc75eaba24b5153ae4970/unicorn/project/__init__.py -------------------------------------------------------------------------------- /unicorn/project/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for project project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /unicorn/project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for project project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.2.3. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "django-insecure-r#*l3%wu1ud=bo@54s_@2=0g5d93y+(k0*kvh2+zxpe_kvync8" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | "django.contrib.admin", 35 | "django.contrib.auth", 36 | "django.contrib.contenttypes", 37 | "django.contrib.sessions", 38 | "django.contrib.messages", 39 | "django.contrib.staticfiles", 40 | "django_unicorn", # required for Unicorn to work 41 | "unicorn", # where the Unicorn components are created 42 | "todo", 43 | ] 44 | 45 | MIDDLEWARE = [ 46 | "django.middleware.security.SecurityMiddleware", 47 | "django.contrib.sessions.middleware.SessionMiddleware", 48 | "django.middleware.common.CommonMiddleware", 49 | "django.middleware.csrf.CsrfViewMiddleware", 50 | "django.contrib.auth.middleware.AuthenticationMiddleware", 51 | "django.contrib.messages.middleware.MessageMiddleware", 52 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 53 | ] 54 | 55 | ROOT_URLCONF = "project.urls" 56 | 57 | TEMPLATES = [ 58 | { 59 | "BACKEND": "django.template.backends.django.DjangoTemplates", 60 | "DIRS": [], 61 | "APP_DIRS": True, 62 | "OPTIONS": { 63 | "context_processors": [ 64 | "django.template.context_processors.debug", 65 | "django.template.context_processors.request", 66 | "django.contrib.auth.context_processors.auth", 67 | "django.contrib.messages.context_processors.messages", 68 | ], 69 | }, 70 | }, 71 | ] 72 | 73 | WSGI_APPLICATION = "project.wsgi.application" 74 | 75 | 76 | # Database 77 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases 78 | 79 | DATABASES = { 80 | "default": { 81 | "ENGINE": "django.db.backends.sqlite3", 82 | "NAME": BASE_DIR / "db.sqlite3", 83 | } 84 | } 85 | 86 | 87 | # Password validation 88 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 89 | 90 | AUTH_PASSWORD_VALIDATORS = [ 91 | { 92 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 93 | }, 94 | { 95 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 96 | }, 97 | { 98 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 99 | }, 100 | { 101 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 102 | }, 103 | ] 104 | 105 | 106 | # Internationalization 107 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 108 | 109 | LANGUAGE_CODE = "en-us" 110 | 111 | TIME_ZONE = "UTC" 112 | 113 | USE_I18N = True 114 | 115 | USE_L10N = True 116 | 117 | USE_TZ = True 118 | 119 | 120 | # Static files (CSS, JavaScript, Images) 121 | # https://docs.djangoproject.com/en/3.2/howto/static-files/ 122 | 123 | STATIC_URL = "/static/" 124 | 125 | # Default primary key field type 126 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 127 | 128 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 129 | -------------------------------------------------------------------------------- /unicorn/project/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | 4 | from todo import views 5 | 6 | 7 | urlpatterns = [ 8 | path("admin/", admin.site.urls), 9 | path("unicorn/", include("django_unicorn.urls")), # required for Unicorn 10 | path("", views.index), 11 | ] 12 | -------------------------------------------------------------------------------- /unicorn/project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /unicorn/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "djangocon-eu-unicorn" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Adam Hill "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.7" 10 | Django = "^3.2.3" 11 | django-unicorn = "^0.27.2" 12 | 13 | [tool.poetry.dev-dependencies] 14 | black = {version = "^21.5b1", allow-prereleases = true} 15 | poethepoet = "^0.10.0" 16 | 17 | [tool.poe.tasks] 18 | m = "./manage.py" 19 | r = "./manage.py runserver 0:8005" 20 | ma = "./manage.py makemigrations" 21 | mi = "./manage.py migrate" 22 | md = ["ma", "mi"] 23 | 24 | [build-system] 25 | requires = ["poetry-core>=1.0.0"] 26 | build-backend = "poetry.core.masonry.api" 27 | -------------------------------------------------------------------------------- /unicorn/todo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/djangocon-eu-2021-conference-talk/33db4b72a25ed435c94bc75eaba24b5153ae4970/unicorn/todo/__init__.py -------------------------------------------------------------------------------- /unicorn/todo/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class Config(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "todo" 7 | -------------------------------------------------------------------------------- /unicorn/todo/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.3 on 2021-05-15 19:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Todo', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('task', models.CharField(max_length=1024)), 19 | ], 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /unicorn/todo/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/djangocon-eu-2021-conference-talk/33db4b72a25ed435c94bc75eaba24b5153ae4970/unicorn/todo/migrations/__init__.py -------------------------------------------------------------------------------- /unicorn/todo/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Todo(models.Model): 5 | task = models.CharField(max_length=1024) 6 | -------------------------------------------------------------------------------- /unicorn/todo/templates/todo/index.html: -------------------------------------------------------------------------------- 1 | {% load unicorn %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | Unicorn 9 | 10 | 11 | {% unicorn_scripts %} 12 | 13 | 14 | {% csrf_token %} 15 | 16 |
17 |
18 |

19 | Unicorn 20 |

21 | 22 | {% unicorn 'todo' %} 23 | 24 |
25 |
26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /unicorn/todo/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | 4 | def index(request): 5 | return render(request, "todo/index.html", {}) 6 | -------------------------------------------------------------------------------- /unicorn/unicorn/components/todo.py: -------------------------------------------------------------------------------- 1 | from django_unicorn.components import UnicornView 2 | from todo.models import Todo 3 | 4 | 5 | class TodoView(UnicornView): 6 | task: str = "" 7 | todos = Todo.objects.none() 8 | 9 | def hydrate(self): 10 | self.todos = Todo.objects.all() 11 | 12 | def add_todo(self): 13 | todo = Todo(task=self.task) 14 | todo.save() 15 | 16 | self.task = "" 17 | -------------------------------------------------------------------------------- /unicorn/unicorn/templates/unicorn/todo.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 | 7 |
8 | 14 |
15 |
16 | 17 |
18 |
19 | 20 |
21 |
22 | 25 |
26 |
27 |
28 | 29 | 30 | 31 |
32 |

33 | Current Tasks 34 |

35 | 36 | {% if not todos %} 37 |

None, yet!

38 | {% else %} 39 |
    40 | {% for todo in todos %} 41 |
  • {{ todo.task }}
  • 42 | {% endfor %} 43 |
44 | {% endif %} 45 |
46 | 47 |
48 | -------------------------------------------------------------------------------- /vuejs/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Gui Talarico 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vuejs/README.md: -------------------------------------------------------------------------------- 1 | Based on [this template](https://github.com/gtalarico/django-vue-template) with some slight modifications. 2 | 3 | # Django Vue Template ✌️ 🐍 4 | 5 | ![Vue Logo](/src/assets/logo-vue.png "Vue Logo") 6 | ![Django Logo](/src/assets/logo-django.png "Django Logo") 7 | 8 | This template is a minimal example for an application using Vue and Django. 9 | 10 | Vue and Django are clearly separated in this project. Vue, Yarn and Webpack handles all frontend logic and bundling assessments. Django and Django REST framework to manage Data Models, Web API and serve static files. 11 | 12 | While it's possible to add endpoints to serve django-rendered html responses, the intention is to use Django primarily for the backend, and have view rendering and routing and handled by Vue + Vue Router as a Single Page Application (SPA). 13 | 14 | Out of the box, Django will serve the application entry point (`index.html` + bundled assets) at `/` , 15 | data at `/api/`, and static files at `/static/`. Django admin panel is also available at `/admin/` and can be extended as needed. 16 | 17 | The application templates from Vue CLI `create` and Django `createproject` are kept as close as possible to their 18 | original state, except where a different configuration is needed for better integration of the two frameworks. 19 | 20 | #### Alternatives 21 | 22 | If this setup is not what you are looking for, you might want look at other similar projects: 23 | 24 | - [ariera/django-vue-template](https://github.com/ariera/django-vue-template) 25 | - [vchaptsev/cookiecutter-django-vue](https://github.com/vchaptsev/cookiecutter-django-vue) 26 | 27 | Prefer Flask? Checkout my [gtalarico/flask-vuejs-template](https://github.com/gtalarico/flask-vuejs-template) 28 | 29 | ### Demo 30 | 31 | [Live Demo](https://django-vue-template-demo.herokuapp.com/) 32 | 33 | ### Includes 34 | 35 | - Django 36 | - Django REST framework 37 | - Django Whitenoise, CDN Ready 38 | - Vue CLI 3 39 | - Vue Router 40 | - Vuex 41 | - Gunicorn 42 | - Configuration for Heroku Deployment 43 | 44 | ### Template Structure 45 | 46 | | Location | Content | 47 | | -------------------- | --------------------------------------------------------------------------------------------- | 48 | | `/backend` | Django Project & Backend Config | 49 | | `/backend/api` | Django App (`/api`) | 50 | | `/src` | Vue App . | 51 | | `/src/main.js` | JS Application Entry Point | 52 | | `/public/index.html` | [Html Application Entry Point](https://cli.vuejs.org/guide/html-and-static-assets.html) (`/`) | 53 | | `/public/static` | Static Assets | 54 | | `/dist/` | Bundled Assets Output (generated at `yarn build`) | 55 | 56 | ## Prerequisites 57 | 58 | Before getting started you should have the following installed and running: 59 | 60 | - [x] Yarn - [instructions](https://yarnpkg.com/en/docs/install) 61 | - [x] Vue CLI 3 - [instructions](https://cli.vuejs.org/guide/installation.html) 62 | - [x] Python 3 - [instructions](https://wiki.python.org/moin/BeginnersGuide) 63 | - [x] Pipenv - [instructions](https://pipenv.readthedocs.io/en/latest/install/#installing-pipenv) 64 | 65 | ## Setup Template 66 | 67 | ``` 68 | $ git clone https://github.com/gtalarico/django-vue-template 69 | $ cd django-vue-template 70 | ``` 71 | 72 | Setup 73 | 74 | ``` 75 | $ yarn install 76 | $ pipenv install --dev && pipenv shell 77 | $ python manage.py migrate 78 | ``` 79 | 80 | ## Running Development Servers 81 | 82 | ``` 83 | $ python manage.py runserver 84 | ``` 85 | 86 | From another tab in the same directory: 87 | 88 | ``` 89 | $ yarn serve 90 | ``` 91 | 92 | The Vue application will be served from [`localhost:8080`](http://localhost:8080/) and the Django API 93 | and static files will be served from [`localhost:8000`](http://localhost:8000/). 94 | 95 | The dual dev server setup allows you to take advantage of 96 | webpack's development server with hot module replacement. 97 | Proxy config in [`vue.config.js`](/vue.config.js) is used to route the requests 98 | back to django's API on port 8000. 99 | 100 | If you would rather run a single dev server, you can run Django's 101 | development server only on `:8000`, and you have to build the Vue app first 102 | and the page will not reload on changes. 103 | 104 | ``` 105 | $ yarn build 106 | $ python manage.py runserver 107 | ``` 108 | 109 | ## Pycharm additional configuration 110 | 111 | Follow this guide to ensure you have pipenv setup 112 | 113 | https://www.jetbrains.com/help/pycharm/pipenv.html 114 | 115 | Click "Edit Configurations" 116 | 117 | Select Django Server under templates 118 | 119 | Click + to create a config from the templates 120 | 121 | In Environment variables add 122 | 123 | ``` 124 | PYTHONUNBUFFERED=1;DJANGO_SETTINGS_MODULE=backend.settings.dev 125 | ``` 126 | 127 | Click Apply then Ok 128 | 129 | ## Deploy 130 | 131 | - Set `ALLOWED_HOSTS` on [`backend.settings.prod`](/backend/settings/prod.py) 132 | 133 | ### Heroku Server 134 | 135 | ``` 136 | $ heroku apps:create django-vue-template-demo 137 | $ heroku git:remote --app django-vue-template-demo 138 | $ heroku buildpacks:add --index 1 heroku/nodejs 139 | $ heroku buildpacks:add --index 2 heroku/python 140 | $ heroku addons:create heroku-postgresql:hobby-dev 141 | $ heroku config:set DJANGO_SETTINGS_MODULE=backend.settings.prod 142 | $ heroku config:set DJANGO_SECRET_KEY='...(your django SECRET_KEY value)...' 143 | 144 | $ git push heroku 145 | ``` 146 | 147 | Heroku's nodejs buildpack will handle install for all the dependencies from the [`package.json`](/package.json) file. 148 | It will then trigger the `postinstall` command which calls `yarn build`. 149 | This will create the bundled `dist` folder which will be served by whitenoise. 150 | 151 | The python buildpack will detect the [`Pipfile`](/Pipfile) and install all the python dependencies. 152 | 153 | The [`Procfile`](/Procfile) will run Django migrations and then launch Django'S app using gunicorn, as recommended by heroku. 154 | 155 | ##### Heroku One Click Deploy 156 | 157 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/gtalarico/django-vue-template) 158 | 159 | ## Static Assets 160 | 161 | See `settings.dev` and [`vue.config.js`](/vue.config.js) for notes on static assets strategy. 162 | 163 | This template implements the approach suggested by Whitenoise Django. 164 | For more details see [WhiteNoise Documentation](http://whitenoise.evans.io/en/stable/django.html) 165 | 166 | It uses Django Whitenoise to serve all static files and Vue bundled files at `/static/`. 167 | While it might seem inefficient, the issue is immediately solved by adding a CDN 168 | with Cloudfront or similar. 169 | Use [`vue.config.js`](/vue.config.js) > `baseUrl` option to set point all your assets to the CDN, 170 | and then set your CDN's origin back to your domains `/static` url. 171 | 172 | Whitenoise will serve static files to your CDN once, but then those assets are cached 173 | and served directly by the CDN. 174 | 175 | This allows for an extremely simple setup without the need for a separate static server. 176 | 177 | [Cloudfront Setup Wiki](https://github.com/gtalarico/django-vue-template/wiki/Setup-CDN-on-Cloud-Front) 178 | -------------------------------------------------------------------------------- /vuejs/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Django VueJs Template", 3 | "description": "", 4 | "repository": "https://github.com/gtalarico/django-vue-template", 5 | "keywords": ["django", "vue", "vuejs", "template", "django rest framework"], 6 | "env": { 7 | "DJANGO_SETTINGS_MODULE": { 8 | "description": "Set Django Settings to Production", 9 | "value": "backend.settings.prod" 10 | } 11 | }, 12 | "engines": { 13 | "yarn": "1.4.0", 14 | "npm": "6.2.0" 15 | }, 16 | "addons": [ 17 | 18 | ], 19 | "buildpacks": [ 20 | { 21 | "url": "heroku/nodejs" 22 | }, 23 | { 24 | "url": "heroku/python" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /vuejs/backend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/djangocon-eu-2021-conference-talk/33db4b72a25ed435c94bc75eaba24b5153ae4970/vuejs/backend/__init__.py -------------------------------------------------------------------------------- /vuejs/backend/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/djangocon-eu-2021-conference-talk/33db4b72a25ed435c94bc75eaba24b5153ae4970/vuejs/backend/api/__init__.py -------------------------------------------------------------------------------- /vuejs/backend/api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AppConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "backend.api" 7 | -------------------------------------------------------------------------------- /vuejs/backend/api/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.3 on 2021-05-15 19:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Todo', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('task', models.CharField(max_length=1024)), 19 | ], 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /vuejs/backend/api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/djangocon-eu-2021-conference-talk/33db4b72a25ed435c94bc75eaba24b5153ae4970/vuejs/backend/api/migrations/__init__.py -------------------------------------------------------------------------------- /vuejs/backend/api/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from rest_framework import serializers 3 | 4 | 5 | class Todo(models.Model): 6 | task = models.CharField(max_length=1024) 7 | 8 | 9 | class TodoSerializer(serializers.HyperlinkedModelSerializer): 10 | class Meta: 11 | model = Todo 12 | fields = ( 13 | "pk", 14 | "task", 15 | ) 16 | -------------------------------------------------------------------------------- /vuejs/backend/api/views.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import TemplateView 2 | from django.views.decorators.cache import never_cache 3 | from rest_framework import viewsets 4 | 5 | from .models import Todo, TodoSerializer 6 | 7 | 8 | # Serves VueJS application 9 | index_view = never_cache(TemplateView.as_view(template_name="index.html")) 10 | 11 | 12 | class TodoViewSet(viewsets.ModelViewSet): 13 | """ 14 | API endpoint that allows todos to be viewed or edited. 15 | """ 16 | 17 | queryset = Todo.objects.all() 18 | serializer_class = TodoSerializer 19 | -------------------------------------------------------------------------------- /vuejs/backend/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/djangocon-eu-2021-conference-talk/33db4b72a25ed435c94bc75eaba24b5153ae4970/vuejs/backend/settings/__init__.py -------------------------------------------------------------------------------- /vuejs/backend/settings/dev.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 4 | SETTINGS_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 5 | BASE_DIR = os.path.dirname(SETTINGS_DIR) 6 | 7 | 8 | # Quick-start development settings - unsuitable for production 9 | # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ 10 | 11 | # SECURITY WARNING: keep the secret key used in production secret! 12 | SECRET_KEY = "verybadsecret!!!" 13 | 14 | # SECURITY WARNING: don't run with debug turned on in production! 15 | DEBUG = True 16 | ALLOWED_HOSTS = ["*"] 17 | 18 | 19 | # Application definition 20 | 21 | INSTALLED_APPS = [ 22 | "django.contrib.admin", 23 | "django.contrib.auth", 24 | "django.contrib.contenttypes", 25 | "django.contrib.sessions", 26 | "django.contrib.messages", 27 | "whitenoise.runserver_nostatic", # < Per Whitenoise, to disable built in 28 | "django.contrib.staticfiles", 29 | "rest_framework", 30 | "backend.api", 31 | ] 32 | 33 | MIDDLEWARE = [ 34 | "django.middleware.security.SecurityMiddleware", 35 | "whitenoise.middleware.WhiteNoiseMiddleware", 36 | "django.contrib.sessions.middleware.SessionMiddleware", 37 | "django.middleware.common.CommonMiddleware", 38 | "django.middleware.csrf.CsrfViewMiddleware", 39 | "django.contrib.auth.middleware.AuthenticationMiddleware", 40 | "django.contrib.messages.middleware.MessageMiddleware", 41 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 42 | ] 43 | 44 | ROOT_URLCONF = "backend.urls" 45 | 46 | TEMPLATES = [ 47 | { 48 | "BACKEND": "django.template.backends.django.DjangoTemplates", 49 | # Add dist to 50 | "DIRS": ["dist", "public"], 51 | "APP_DIRS": True, 52 | "OPTIONS": { 53 | "context_processors": [ 54 | "django.template.context_processors.debug", 55 | "django.template.context_processors.request", 56 | "django.contrib.auth.context_processors.auth", 57 | "django.contrib.messages.context_processors.messages", 58 | ], 59 | }, 60 | }, 61 | ] 62 | 63 | WSGI_APPLICATION = "backend.wsgi.application" 64 | 65 | 66 | # Database 67 | # https://docs.djangoproject.com/en/2.1/ref/settings/#databases 68 | 69 | DATABASES = { 70 | "default": { 71 | "ENGINE": "django.db.backends.sqlite3", 72 | "NAME": os.path.join(BASE_DIR, "db.sqlite3"), 73 | } 74 | } 75 | 76 | 77 | # Password validation 78 | # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators 79 | 80 | AUTH_PASSWORD_VALIDATORS = [ 81 | { 82 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 83 | }, 84 | { 85 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 86 | }, 87 | { 88 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 89 | }, 90 | { 91 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 92 | }, 93 | ] 94 | 95 | 96 | # Internationalization 97 | # https://docs.djangoproject.com/en/2.1/topics/i18n/ 98 | 99 | LANGUAGE_CODE = "en-us" 100 | TIME_ZONE = "UTC" 101 | USE_I18N = True 102 | USE_L10N = True 103 | USE_TZ = True 104 | 105 | 106 | # Static files (CSS, JavaScript, Images) 107 | # https://docs.djangoproject.com/en/2.1/howto/static-files/ 108 | 109 | # When Vue Builds, path will be `/static/css/...` so we will have Django Serve 110 | # In Production, it's recommended use an alternative approach such as: 111 | # http://whitenoise.evans.io/en/stable/django.html?highlight=django 112 | 113 | MIDDLEWARE_CLASSES = ("whitenoise.middleware.WhiteNoiseMiddleware",) 114 | 115 | STATIC_URL = "/static/" 116 | # Place static in the same location as webpack build files 117 | STATIC_ROOT = os.path.join(BASE_DIR, "dist", "static") 118 | STATICFILES_DIRS = [] 119 | 120 | 121 | ########## 122 | # STATIC # 123 | ########## 124 | 125 | STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" 126 | 127 | # Insert Whitenoise Middleware at top but below Security Middleware 128 | # MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware',) 129 | # http://whitenoise.evans.io/en/stable/django.html#make-sure-staticfiles-is-configured-correctly 130 | -------------------------------------------------------------------------------- /vuejs/backend/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from rest_framework import routers 3 | 4 | from .api.views import index_view, TodoViewSet 5 | 6 | router = routers.DefaultRouter() 7 | router.register("todos", TodoViewSet) 8 | 9 | urlpatterns = [ 10 | path("", index_view, name="index"), 11 | path("api/", include(router.urls)), 12 | ] 13 | -------------------------------------------------------------------------------- /vuejs/backend/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | # This will set production as default, but we must still set it with an 15 | # ENV on heroku to ensure that the migrate command runs agains the correct DB 16 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings.prod") 17 | 18 | application = get_wsgi_application() 19 | -------------------------------------------------------------------------------- /vuejs/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings.dev") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /vuejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "django-vue", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve --port=8001", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.18.0", 12 | "js-cookie": "^2.2.0", 13 | "vue": "^2.5.17", 14 | "vue-router": "^3.0.1", 15 | "vuex": "^3.0.1" 16 | }, 17 | "devDependencies": { 18 | "@vue/cli-plugin-babel": "^3.0.0-rc.12", 19 | "@vue/cli-plugin-eslint": "^3.0.0-rc.12", 20 | "@vue/cli-service": "^3.0.0-rc.12", 21 | "vue-template-compiler": "^2.5.17" 22 | }, 23 | "babel": { 24 | "presets": [ 25 | "@vue/app" 26 | ] 27 | }, 28 | "eslintConfig": { 29 | "root": true, 30 | "env": { 31 | "node": true 32 | }, 33 | "extends": [ 34 | "plugin:vue/essential", 35 | "eslint:recommended" 36 | ], 37 | "rules": {}, 38 | "parserOptions": { 39 | "parser": "babel-eslint" 40 | } 41 | }, 42 | "postcss": { 43 | "plugins": { 44 | "autoprefixer": {} 45 | } 46 | }, 47 | "browserslist": [ 48 | "> 1%", 49 | "last 2 versions", 50 | "not ie <= 8" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /vuejs/poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "appdirs" 3 | version = "1.4.4" 4 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 5 | category = "dev" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [[package]] 10 | name = "asgiref" 11 | version = "3.3.4" 12 | description = "ASGI specs, helper code, and adapters" 13 | category = "main" 14 | optional = false 15 | python-versions = ">=3.6" 16 | 17 | [package.dependencies] 18 | typing-extensions = {version = "*", markers = "python_version < \"3.8\""} 19 | 20 | [package.extras] 21 | tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] 22 | 23 | [[package]] 24 | name = "black" 25 | version = "21.5b1" 26 | description = "The uncompromising code formatter." 27 | category = "dev" 28 | optional = false 29 | python-versions = ">=3.6.2" 30 | 31 | [package.dependencies] 32 | appdirs = "*" 33 | click = ">=7.1.2" 34 | mypy-extensions = ">=0.4.3" 35 | pathspec = ">=0.8.1,<1" 36 | regex = ">=2020.1.8" 37 | toml = ">=0.10.1" 38 | typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""} 39 | typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} 40 | 41 | [package.extras] 42 | colorama = ["colorama (>=0.4.3)"] 43 | d = ["aiohttp (>=3.6.0)", "aiohttp-cors"] 44 | python2 = ["typed-ast (>=1.4.2)"] 45 | 46 | [[package]] 47 | name = "click" 48 | version = "8.0.0" 49 | description = "Composable command line interface toolkit" 50 | category = "dev" 51 | optional = false 52 | python-versions = ">=3.6" 53 | 54 | [package.dependencies] 55 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 56 | 57 | [[package]] 58 | name = "colorama" 59 | version = "0.4.4" 60 | description = "Cross-platform colored terminal text." 61 | category = "dev" 62 | optional = false 63 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 64 | 65 | [[package]] 66 | name = "django" 67 | version = "3.2.3" 68 | description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." 69 | category = "main" 70 | optional = false 71 | python-versions = ">=3.6" 72 | 73 | [package.dependencies] 74 | asgiref = ">=3.3.2,<4" 75 | pytz = "*" 76 | sqlparse = ">=0.2.2" 77 | 78 | [package.extras] 79 | argon2 = ["argon2-cffi (>=19.1.0)"] 80 | bcrypt = ["bcrypt"] 81 | 82 | [[package]] 83 | name = "djangorestframework" 84 | version = "3.12.4" 85 | description = "Web APIs for Django, made easy." 86 | category = "main" 87 | optional = false 88 | python-versions = ">=3.5" 89 | 90 | [package.dependencies] 91 | django = ">=2.2" 92 | 93 | [[package]] 94 | name = "mypy-extensions" 95 | version = "0.4.3" 96 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 97 | category = "dev" 98 | optional = false 99 | python-versions = "*" 100 | 101 | [[package]] 102 | name = "pastel" 103 | version = "0.2.1" 104 | description = "Bring colors to your terminal." 105 | category = "dev" 106 | optional = false 107 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 108 | 109 | [[package]] 110 | name = "pathspec" 111 | version = "0.8.1" 112 | description = "Utility library for gitignore style pattern matching of file paths." 113 | category = "dev" 114 | optional = false 115 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 116 | 117 | [[package]] 118 | name = "poethepoet" 119 | version = "0.10.0" 120 | description = "A task runner that works well with poetry." 121 | category = "dev" 122 | optional = false 123 | python-versions = ">=3.6,<4.0" 124 | 125 | [package.dependencies] 126 | pastel = ">=0.2.0,<0.3.0" 127 | tomlkit = ">=0.6.0,<1.0.0" 128 | 129 | [[package]] 130 | name = "pytz" 131 | version = "2021.1" 132 | description = "World timezone definitions, modern and historical" 133 | category = "main" 134 | optional = false 135 | python-versions = "*" 136 | 137 | [[package]] 138 | name = "regex" 139 | version = "2021.4.4" 140 | description = "Alternative regular expression module, to replace re." 141 | category = "dev" 142 | optional = false 143 | python-versions = "*" 144 | 145 | [[package]] 146 | name = "sqlparse" 147 | version = "0.4.1" 148 | description = "A non-validating SQL parser." 149 | category = "main" 150 | optional = false 151 | python-versions = ">=3.5" 152 | 153 | [[package]] 154 | name = "toml" 155 | version = "0.10.2" 156 | description = "Python Library for Tom's Obvious, Minimal Language" 157 | category = "dev" 158 | optional = false 159 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 160 | 161 | [[package]] 162 | name = "tomlkit" 163 | version = "0.7.0" 164 | description = "Style preserving TOML library" 165 | category = "dev" 166 | optional = false 167 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 168 | 169 | [[package]] 170 | name = "typed-ast" 171 | version = "1.4.3" 172 | description = "a fork of Python 2 and 3 ast modules with type comment support" 173 | category = "dev" 174 | optional = false 175 | python-versions = "*" 176 | 177 | [[package]] 178 | name = "typing-extensions" 179 | version = "3.10.0.0" 180 | description = "Backported and Experimental Type Hints for Python 3.5+" 181 | category = "main" 182 | optional = false 183 | python-versions = "*" 184 | 185 | [[package]] 186 | name = "whitenoise" 187 | version = "5.2.0" 188 | description = "Radically simplified static file serving for WSGI applications" 189 | category = "main" 190 | optional = false 191 | python-versions = ">=3.5, <4" 192 | 193 | [package.extras] 194 | brotli = ["brotli"] 195 | 196 | [metadata] 197 | lock-version = "1.1" 198 | python-versions = "^3.7" 199 | content-hash = "c022357b544a2d4947f8806b76d7fa5622883803a52d1c474cfa42f6c4186990" 200 | 201 | [metadata.files] 202 | appdirs = [ 203 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 204 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 205 | ] 206 | asgiref = [ 207 | {file = "asgiref-3.3.4-py3-none-any.whl", hash = "sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee"}, 208 | {file = "asgiref-3.3.4.tar.gz", hash = "sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78"}, 209 | ] 210 | black = [ 211 | {file = "black-21.5b1-py3-none-any.whl", hash = "sha256:8a60071a0043876a4ae96e6c69bd3a127dad2c1ca7c8083573eb82f92705d008"}, 212 | {file = "black-21.5b1.tar.gz", hash = "sha256:23695358dbcb3deafe7f0a3ad89feee5999a46be5fec21f4f1d108be0bcdb3b1"}, 213 | ] 214 | click = [ 215 | {file = "click-8.0.0-py3-none-any.whl", hash = "sha256:e90e62ced43dc8105fb9a26d62f0d9340b5c8db053a814e25d95c19873ae87db"}, 216 | {file = "click-8.0.0.tar.gz", hash = "sha256:7d8c289ee437bcb0316820ccee14aefcb056e58d31830ecab8e47eda6540e136"}, 217 | ] 218 | colorama = [ 219 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 220 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 221 | ] 222 | django = [ 223 | {file = "Django-3.2.3-py3-none-any.whl", hash = "sha256:7e0a1393d18c16b503663752a8b6790880c5084412618990ce8a81cc908b4962"}, 224 | {file = "Django-3.2.3.tar.gz", hash = "sha256:13ac78dbfd189532cad8f383a27e58e18b3d33f80009ceb476d7fcbfc5dcebd8"}, 225 | ] 226 | djangorestframework = [ 227 | {file = "djangorestframework-3.12.4-py3-none-any.whl", hash = "sha256:6d1d59f623a5ad0509fe0d6bfe93cbdfe17b8116ebc8eda86d45f6e16e819aaf"}, 228 | {file = "djangorestframework-3.12.4.tar.gz", hash = "sha256:f747949a8ddac876e879190df194b925c177cdeb725a099db1460872f7c0a7f2"}, 229 | ] 230 | mypy-extensions = [ 231 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 232 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 233 | ] 234 | pastel = [ 235 | {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"}, 236 | {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, 237 | ] 238 | pathspec = [ 239 | {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, 240 | {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, 241 | ] 242 | poethepoet = [ 243 | {file = "poethepoet-0.10.0-py3-none-any.whl", hash = "sha256:6fb3021603d4421c6fcc40072bbcf150a6c52ef70ff4d3be089b8b04e015ef5a"}, 244 | {file = "poethepoet-0.10.0.tar.gz", hash = "sha256:70b97cb194b978dc464c70793e85e6f746cddf82b84a38bfb135946ad71ae19c"}, 245 | ] 246 | pytz = [ 247 | {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, 248 | {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, 249 | ] 250 | regex = [ 251 | {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, 252 | {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, 253 | {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, 254 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, 255 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, 256 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, 257 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, 258 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, 259 | {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, 260 | {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, 261 | {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, 262 | {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, 263 | {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, 264 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, 265 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, 266 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, 267 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, 268 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, 269 | {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, 270 | {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, 271 | {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, 272 | {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, 273 | {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, 274 | {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, 275 | {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, 276 | {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, 277 | {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, 278 | {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, 279 | {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, 280 | {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, 281 | {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, 282 | {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, 283 | {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, 284 | {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, 285 | {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, 286 | {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, 287 | {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, 288 | {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, 289 | {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, 290 | {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, 291 | {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, 292 | ] 293 | sqlparse = [ 294 | {file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"}, 295 | {file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"}, 296 | ] 297 | toml = [ 298 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 299 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 300 | ] 301 | tomlkit = [ 302 | {file = "tomlkit-0.7.0-py2.py3-none-any.whl", hash = "sha256:6babbd33b17d5c9691896b0e68159215a9387ebfa938aa3ac42f4a4beeb2b831"}, 303 | {file = "tomlkit-0.7.0.tar.gz", hash = "sha256:ac57f29693fab3e309ea789252fcce3061e19110085aa31af5446ca749325618"}, 304 | ] 305 | typed-ast = [ 306 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, 307 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, 308 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, 309 | {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, 310 | {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, 311 | {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, 312 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, 313 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, 314 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, 315 | {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, 316 | {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, 317 | {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, 318 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, 319 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, 320 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, 321 | {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, 322 | {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, 323 | {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, 324 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, 325 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, 326 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, 327 | {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, 328 | {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, 329 | {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, 330 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, 331 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, 332 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, 333 | {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, 334 | {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, 335 | {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, 336 | ] 337 | typing-extensions = [ 338 | {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, 339 | {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, 340 | {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, 341 | ] 342 | whitenoise = [ 343 | {file = "whitenoise-5.2.0-py2.py3-none-any.whl", hash = "sha256:05d00198c777028d72d8b0bbd234db605ef6d60e9410125124002518a48e515d"}, 344 | {file = "whitenoise-5.2.0.tar.gz", hash = "sha256:05ce0be39ad85740a78750c86a93485c40f08ad8c62a6006de0233765996e5c7"}, 345 | ] 346 | -------------------------------------------------------------------------------- /vuejs/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | VueJS 7 | 8 | 9 | 10 | 11 |
12 |
13 |

14 | VueJS 15 |

16 | 17 |
18 | 19 |
20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /vuejs/public/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/djangocon-eu-2021-conference-talk/33db4b72a25ed435c94bc75eaba24b5153ae4970/vuejs/public/static/favicon.ico -------------------------------------------------------------------------------- /vuejs/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "vuejs" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Adam Hill "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.7" 10 | Django = "^3.2.3" 11 | djangorestframework = "^3.12.4" 12 | whitenoise = "^5.2.0" 13 | 14 | [tool.poetry.dev-dependencies] 15 | black = {version = "^21.5b1", allow-prereleases = true} 16 | poethepoet = "^0.10.0" 17 | 18 | [tool.poe.tasks] 19 | m = "./manage.py" 20 | r = "./manage.py runserver 0:8000" 21 | ma = "./manage.py makemigrations" 22 | mi = "./manage.py migrate" 23 | md = ["ma", "mi"] 24 | 25 | [build-system] 26 | requires = ["poetry-core>=1.0.0"] 27 | build-backend = "poetry.core.masonry.api" 28 | -------------------------------------------------------------------------------- /vuejs/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /vuejs/src/components/Todos.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 68 | -------------------------------------------------------------------------------- /vuejs/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "@/App.vue"; 3 | 4 | import store from "@/store"; 5 | import router from "@/router"; 6 | 7 | Vue.config.productionTip = false; 8 | 9 | const vue = new Vue({ 10 | router, 11 | store, 12 | render: (h) => h(App), 13 | }); 14 | 15 | vue.$mount("#app"); 16 | -------------------------------------------------------------------------------- /vuejs/src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Router from "vue-router"; 3 | import Todos from "@/components/Todos"; 4 | 5 | Vue.use(Router); 6 | 7 | export default new Router({ 8 | routes: [ 9 | { 10 | path: "/", 11 | name: "todos", 12 | component: Todos, 13 | }, 14 | ], 15 | }); 16 | -------------------------------------------------------------------------------- /vuejs/src/services/api.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import Cookies from "js-cookie"; 3 | 4 | export default axios.create({ 5 | baseURL: "/api", 6 | timeout: 5000, 7 | headers: { 8 | "Content-Type": "application/json", 9 | "X-CSRFToken": Cookies.get("csrftoken"), 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /vuejs/src/services/todoService.js: -------------------------------------------------------------------------------- 1 | import api from "@/services/api"; 2 | 3 | export default { 4 | fetchTodos() { 5 | return api.get(`todos/`).then((response) => response.data); 6 | }, 7 | postTodo(payload) { 8 | return api.post(`todos/`, payload).then((response) => response.data); 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /vuejs/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | import todos from "./modules/todos"; 4 | 5 | Vue.use(Vuex); 6 | 7 | export default new Vuex.Store({ 8 | modules: { 9 | todos, 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /vuejs/src/store/modules/todos.js: -------------------------------------------------------------------------------- 1 | import todoService from "../../services/todoService"; 2 | 3 | const state = { 4 | todos: [], 5 | }; 6 | 7 | const getters = { 8 | todos: (state) => { 9 | return state.todos; 10 | }, 11 | }; 12 | 13 | const actions = { 14 | getTodos({ commit }) { 15 | todoService.fetchTodos().then((todos) => { 16 | commit("setTodos", todos); 17 | }); 18 | }, 19 | addTodo({ commit }, todo) { 20 | todoService.postTodo(todo).then(() => { 21 | commit("addTodo", todo); 22 | }); 23 | }, 24 | }; 25 | 26 | const mutations = { 27 | setTodos(state, todos) { 28 | state.todos = todos; 29 | }, 30 | addTodo(state, todo) { 31 | state.todos.push(todo); 32 | }, 33 | }; 34 | 35 | export default { 36 | namespaced: true, 37 | state, 38 | getters, 39 | actions, 40 | mutations, 41 | }; 42 | -------------------------------------------------------------------------------- /vuejs/vue.config.js: -------------------------------------------------------------------------------- 1 | // const IS_PRODUCTION = process.env.NODE_ENV === 'production' 2 | 3 | module.exports = { 4 | outputDir: 'dist', 5 | assetsDir: 'static', 6 | // baseUrl: IS_PRODUCTION 7 | // ? 'http://cdn123.com' 8 | // : '/', 9 | // For Production, replace set baseUrl to CDN 10 | // And set the CDN origin to `yourdomain.com/static` 11 | // Whitenoise will serve once to CDN which will then cache 12 | // and distribute 13 | devServer: { 14 | proxy: { 15 | '/api*': { 16 | // Forward frontend dev server request for /api to django dev server 17 | target: 'http://localhost:8000/', 18 | } 19 | } 20 | } 21 | } 22 | --------------------------------------------------------------------------------