Remove files for web api
Make modifications to run on ec2
@ -1,5 +0,0 @@
|
||||
[run]
|
||||
include = geeksbot_v2/*
|
||||
omit = *migrations*, *tests*
|
||||
plugins =
|
||||
django_coverage_plugin
|
||||
@ -1,4 +0,0 @@
|
||||
.*
|
||||
!.coveragerc
|
||||
!.env
|
||||
!.pylintrc
|
||||
@ -1,33 +0,0 @@
|
||||
# http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{py,rst,ini}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.py]
|
||||
line_length=120
|
||||
known_first_party=geeksbot_v2
|
||||
multi_line_output=3
|
||||
default_section=THIRDPARTY
|
||||
|
||||
[*.{html,css,scss,json,yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[nginx.conf]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
1
.gitattributes
vendored
@ -1 +0,0 @@
|
||||
* text=auto
|
||||
14
.pylintrc
@ -1,14 +0,0 @@
|
||||
[MASTER]
|
||||
load-plugins=pylint_django
|
||||
|
||||
[FORMAT]
|
||||
max-line-length=120
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
disable=missing-docstring,invalid-name
|
||||
|
||||
[DESIGN]
|
||||
max-parents=13
|
||||
|
||||
[TYPECHECK]
|
||||
generated-members=REQUEST,acl_users,aq_parent,"[a-zA-Z]+_set{1,2}",save,delete
|
||||
3
.vscode/settings.json
vendored
@ -1,3 +0,0 @@
|
||||
{
|
||||
"python.pythonPath": "/home/dustyp/.virtualenvs/geeksbot/bin/python"
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
FROM python:3.7-alpine AS geeksbot-base
|
||||
FROM python:3.8-alpine AS geeksbot
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
@ -27,4 +27,15 @@ ENV LANG C.UTF-8
|
||||
RUN pip install --upgrade pip
|
||||
RUN pip install virtualenv
|
||||
|
||||
RUN apk update && apk add postgresql-client
|
||||
WORKDIR /code
|
||||
|
||||
COPY requirements/base.txt .
|
||||
COPY requirements/production.txt .
|
||||
COPY requirements/geeksbot.txt .
|
||||
COPY .env .
|
||||
COPY entrypoint .
|
||||
|
||||
RUN pip install -r production.txt
|
||||
RUN pip install -r geeksbot.txt
|
||||
|
||||
CMD ["./entrypoint"]
|
||||
153
docs/Makefile
@ -1,153 +0,0 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/geeksbot_v2.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/geeksbot_v2.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/geeksbot_v2"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/geeksbot_v2"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
@ -1 +0,0 @@
|
||||
# Included so that Django's startproject comment runs against the docs directory
|
||||
255
docs/conf.py
@ -1,255 +0,0 @@
|
||||
# geeksbot documentation build configuration file, created by
|
||||
# sphinx-quickstart.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = []
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = ".rst"
|
||||
|
||||
# The encoding of source files.
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = "geeksbot"
|
||||
copyright = """2019, Dustin Pianalto"""
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = "0.1"
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = "0.1"
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
# language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
# today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
# today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ["_build"]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
# default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
# add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
# add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
# show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = "sphinx"
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
# modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = "default"
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
# html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
# html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
# html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
# html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
# html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ["_static"]
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
# html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
# html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
# html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
# html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
# html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
# html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
# html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
# html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
# html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = "geeksbot_v2doc"
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
# 'papersize': 'letterpaper',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
# 'pointsize': '10pt',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
# 'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
(
|
||||
"index",
|
||||
"geeksbot_v2.tex",
|
||||
"geeksbot Documentation",
|
||||
"""Dustin Pianalto""",
|
||||
"manual",
|
||||
)
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
# latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
# latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
# latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
# latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
# latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(
|
||||
"index",
|
||||
"geeksbot_v2",
|
||||
"geeksbot Documentation",
|
||||
["""Dustin Pianalto"""],
|
||||
1,
|
||||
)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
# man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(
|
||||
"index",
|
||||
"geeksbot_v2",
|
||||
"geeksbot Documentation",
|
||||
"""Dustin Pianalto""",
|
||||
"geeksbot",
|
||||
"""Version 2 of Geeksbot""",
|
||||
"Miscellaneous",
|
||||
)
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
# texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
# texinfo_show_urls = 'footnote'
|
||||
@ -1,20 +0,0 @@
|
||||
.. geeksbot documentation master file, created by
|
||||
sphinx-quickstart.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
geeksbot Project Documentation
|
||||
====================================================================
|
||||
|
||||
Table of Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
Indices & Tables
|
||||
================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
190
docs/make.bat
@ -1,190 +0,0 @@
|
||||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\geeksbot_v2.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\geeksbot_v2.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
||||
@ -1,64 +0,0 @@
|
||||
Docker Remote Debugging
|
||||
=======================
|
||||
|
||||
To connect to python remote interpreter inside docker, you have to make sure first, that Pycharm is aware of your docker.
|
||||
|
||||
Go to *Settings > Build, Execution, Deployment > Docker*. If you are on linux, you can use docker directly using its socket `unix:///var/run/docker.sock`, if you are on Windows or Mac, make sure that you have docker-machine installed, then you can simply *Import credentials from Docker Machine*.
|
||||
|
||||
.. image:: images/1.png
|
||||
|
||||
Configure Remote Python Interpreter
|
||||
-----------------------------------
|
||||
|
||||
This repository comes with already prepared "Run/Debug Configurations" for docker.
|
||||
|
||||
.. image:: images/2.png
|
||||
|
||||
But as you can see, at the beginning there is something wrong with them. They have red X on django icon, and they cannot be used, without configuring remote python interpteter. To do that, you have to go to *Settings > Build, Execution, Deployment* first.
|
||||
|
||||
|
||||
Next, you have to add new remote python interpreter, based on already tested deployment settings. Go to *Settings > Project > Project Interpreter*. Click on the cog icon, and click *Add Remote*.
|
||||
|
||||
.. image:: images/3.png
|
||||
|
||||
Switch to *Docker Compose* and select `local.yml` file from directory of your project, next set *Service name* to `django`
|
||||
|
||||
.. image:: images/4.png
|
||||
|
||||
Having that, click *OK*. Close *Settings* panel, and wait few seconds...
|
||||
|
||||
.. image:: images/7.png
|
||||
|
||||
After few seconds, all *Run/Debug Configurations* should be ready to use.
|
||||
|
||||
.. image:: images/8.png
|
||||
|
||||
**Things you can do with provided configuration**:
|
||||
|
||||
* run and debug python code
|
||||
.. image:: images/f1.png
|
||||
* run and debug tests
|
||||
.. image:: images/f2.png
|
||||
.. image:: images/f3.png
|
||||
* run and debug migrations or different django management commands
|
||||
.. image:: images/f4.png
|
||||
* and many others..
|
||||
|
||||
Known issues
|
||||
------------
|
||||
|
||||
* Pycharm hangs on "Connecting to Debugger"
|
||||
|
||||
.. image:: images/issue1.png
|
||||
|
||||
This might be fault of your firewall. Take a look on this ticket - https://youtrack.jetbrains.com/issue/PY-18913
|
||||
|
||||
* Modified files in `.idea` directory
|
||||
|
||||
Most of the files from `.idea/` were added to `.gitignore` with a few exceptions, which were made, to provide "ready to go" configuration. After adding remote interpreter some of these files are altered by PyCharm:
|
||||
|
||||
.. image:: images/issue2.png
|
||||
|
||||
In theory you can remove them from repository, but then, other people will lose a ability to initialize a project from provided configurations as you did. To get rid of this annoying state, you can run command::
|
||||
|
||||
$ git update-index --assume-unchanged geeksbot_v2.iml
|
||||
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 177 KiB |
|
Before Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 249 KiB |
|
Before Width: | Height: | Size: 229 KiB |
|
Before Width: | Height: | Size: 230 KiB |
|
Before Width: | Height: | Size: 222 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 11 KiB |
13
entrypoint
Normal file
@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
set -o nounset
|
||||
|
||||
source .env
|
||||
|
||||
# Check Redis is up
|
||||
|
||||
# Check Web API is up with simple curl of hostcheck
|
||||
|
||||
python -m geeksbot
|
||||
@ -1,7 +0,0 @@
|
||||
__version__ = "2.0.0"
|
||||
__version_info__ = tuple(
|
||||
[
|
||||
int(num) if num.isdigit() else num
|
||||
for num in __version__.replace("-", ".", 1).split(".")
|
||||
]
|
||||
)
|
||||
@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@ -1,10 +0,0 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import ChannelsAPI, ChannelDetail, AdminChannelAPI
|
||||
|
||||
app_name = "channels_api"
|
||||
urlpatterns = [
|
||||
path("", view=ChannelsAPI.as_view(), name="list"),
|
||||
path("<str:id>/", view=ChannelDetail.as_view(), name='detail'),
|
||||
path("<str:guild_id>/admin/", view=AdminChannelAPI.as_view(), name='admin')
|
||||
]
|
||||
@ -1,7 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class ChannelsConfig(AppConfig):
|
||||
name = 'geeksbot_v2.channels'
|
||||
verbose_name = _("Channels")
|
||||
@ -1,23 +0,0 @@
|
||||
# Generated by Django 2.2.4 on 2019-09-20 21:39
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('guilds', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Channel',
|
||||
fields=[
|
||||
('id', models.CharField(max_length=30, primary_key=True, serialize=False)),
|
||||
('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@ -1,28 +0,0 @@
|
||||
# Generated by Django 2.2.4 on 2019-09-21 02:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('channels', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='channel',
|
||||
name='admin',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='channel',
|
||||
name='default',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='channel',
|
||||
name='new_patron',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@ -1,85 +0,0 @@
|
||||
from django.db import models
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from rest_framework import status
|
||||
|
||||
from geeksbot_v2.guilds.models import Guild
|
||||
from .utils import create_error_response
|
||||
from .utils import create_success_response
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
||||
class Channel(models.Model):
|
||||
id = models.CharField(max_length=30, primary_key=True)
|
||||
guild = models.ForeignKey(Guild, on_delete=models.CASCADE)
|
||||
default = models.BooleanField(default=False)
|
||||
new_patron = models.BooleanField(default=False)
|
||||
admin = models.BooleanField(default=False)
|
||||
|
||||
def update_channel(self, data):
|
||||
if data.get('default'):
|
||||
try:
|
||||
existing_default = self.get_guild_channels(self.guild).get(default=True)
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
else:
|
||||
existing_default.default = False
|
||||
existing_default.save()
|
||||
finally:
|
||||
self.default = data.get('default')
|
||||
if data.get('new_patron'):
|
||||
self.new_patron = data.get('new_patron')
|
||||
if data.get('admin'):
|
||||
self.admin = data.get('admin')
|
||||
|
||||
self.save()
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def add_new_channel(cls, data):
|
||||
id = data.get('id')
|
||||
if id and cls.get_channel_by_id(id):
|
||||
return create_error_response('Channel Already Exists',
|
||||
status=status.HTTP_409_CONFLICT)
|
||||
guild_id = data.get('guild')
|
||||
if not (id and guild_id):
|
||||
return create_error_response('ID and Guild are required',
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
guild = Guild.get_guild_by_id(guild_id)
|
||||
if not isinstance(guild, Guild):
|
||||
return create_error_response('Guild Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
channel = cls(
|
||||
id=id,
|
||||
guild=guild,
|
||||
default=data.get('default', False),
|
||||
new_patron=data.get('new_patron', False),
|
||||
admin=data.get('admin', False)
|
||||
)
|
||||
channel.save()
|
||||
return create_success_response(channel, status.HTTP_201_CREATED, many=False)
|
||||
|
||||
@classmethod
|
||||
def get_channel_by_id(cls, guild_id, channel_id):
|
||||
try:
|
||||
return cls.get_guild_channels(guild_id).get(id=channel_id)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_guild_channels(cls, guild):
|
||||
if isinstance(guild, Guild):
|
||||
return cls.objects.filter(guild=guild)
|
||||
elif isinstance(guild, (str, int)):
|
||||
return cls.objects.filter(guild__id=guild)
|
||||
|
||||
@classmethod
|
||||
def get_admin_channel(cls, guild_id):
|
||||
try:
|
||||
return cls.get_guild_channels(guild_id).get(admin=True)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return str(id)
|
||||
@ -1,9 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import Channel
|
||||
|
||||
|
||||
class ChannelSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Channel
|
||||
fields = "__all__"
|
||||
@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@ -1,14 +0,0 @@
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
||||
|
||||
def create_error_response(msg, status=status.HTTP_404_NOT_FOUND):
|
||||
return Response({'details': msg},
|
||||
status=status)
|
||||
|
||||
|
||||
def create_success_response(channel_data, status, many: bool = False):
|
||||
from .serializers import ChannelSerializer
|
||||
|
||||
return Response(ChannelSerializer(channel_data, many=many).data,
|
||||
status=status)
|
||||
@ -1,97 +0,0 @@
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import status
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
from geeksbot_v2.utils.api_utils import PaginatedAPIView
|
||||
from .models import Channel
|
||||
from .utils import create_error_response
|
||||
from .utils import create_success_response
|
||||
|
||||
# Create your views here.
|
||||
|
||||
# API Views
|
||||
|
||||
|
||||
class ChannelsAPI(PaginatedAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, guild_id, format=None):
|
||||
channels = Channel.get_guild_channels(guild_id)
|
||||
page = self.paginate_queryset(channels)
|
||||
if page is not None:
|
||||
return create_success_response(page, status.HTTP_200_OK, many=True)
|
||||
|
||||
return create_success_response(channels, status.HTTP_200_OK, many=True)
|
||||
|
||||
def post(self, request, format=None):
|
||||
data = dict(request.data)
|
||||
return Channel.add_new_channel(data)
|
||||
|
||||
|
||||
class AdminChannelAPI(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, guild_id, format=None):
|
||||
channel = Channel.get_admin_channel(guild_id)
|
||||
if channel:
|
||||
return create_success_response(channel, status=status.HTTP_200_OK)
|
||||
return create_error_response('There is no admin channel configured for that guild',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def put(self, request, guild_id, format=None):
|
||||
data = dict(request.data)
|
||||
channel = Channel.get_channel_by_id(guild_id, data['channel'])
|
||||
if channel:
|
||||
channel = channel.update_channel({'admin': True})
|
||||
return create_success_response(channel, status=status.HTTP_202_ACCEPTED)
|
||||
return create_error_response("That channel does not exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def delete(self, request, guild_id, format=None):
|
||||
channel = Channel.get_admin_channel(guild_id)
|
||||
if channel:
|
||||
channel = channel.update_channel({'admin': False})
|
||||
return create_success_response(channel, status=status.HTTP_202_ACCEPTED)
|
||||
return create_error_response("There is no admin channel configured",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
class ChannelDetail(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, guild_id, channel_id, format=None):
|
||||
try:
|
||||
guild = Channel.get_channel_by_id(guild_id, channel_id)
|
||||
except ObjectDoesNotExist:
|
||||
return create_error_response("Channel Does not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
else:
|
||||
return create_success_response(guild,
|
||||
status=status.HTTP_200_OK)
|
||||
|
||||
def put(self, request, guild_id, channel_id, format=None):
|
||||
channel = Channel.get_channel_by_id(guild_id, channel_id)
|
||||
|
||||
if channel:
|
||||
data = dict(request.data)
|
||||
channel = channel.update_channel(data)
|
||||
return create_success_response(channel,
|
||||
status=status.HTTP_202_ACCEPTED)
|
||||
else:
|
||||
return create_error_response('Channel Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def delete(self, request, guild_id, channel_id, format=None):
|
||||
channel = Channel.get_channel_by_id(guild_id, channel_id)
|
||||
|
||||
if channel:
|
||||
# data = dict(request.data)
|
||||
# TODO Add a check to verify user is allowed to delete...
|
||||
# Possibly in object permissions...
|
||||
channel.delete()
|
||||
return create_success_response(guild,
|
||||
status=status.HTTP_200_OK)
|
||||
else:
|
||||
return create_error_response('Channel Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
@ -1,290 +0,0 @@
|
||||
"""
|
||||
Base settings to build other settings files upon.
|
||||
"""
|
||||
|
||||
import environ
|
||||
|
||||
ROOT_DIR = (
|
||||
environ.Path(__file__) - 3
|
||||
) # (geeksbot_v2/config/settings/base.py - 3 = geeksbot_v2/)
|
||||
APPS_DIR = ROOT_DIR
|
||||
|
||||
env = environ.Env()
|
||||
|
||||
READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=True)
|
||||
if READ_DOT_ENV_FILE:
|
||||
# OS environment variables take precedence over variables from .env
|
||||
env.read_env(str(ROOT_DIR.path(".env")))
|
||||
|
||||
# GENERAL
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
||||
DEBUG = env.bool("DJANGO_DEBUG", False)
|
||||
# Local time zone. Choices are
|
||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||
# though not all of them may be available with every OS.
|
||||
# In Windows, this must be set to your system time zone.
|
||||
TIME_ZONE = "UTC"
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#language-code
|
||||
LANGUAGE_CODE = "en-us"
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#site-id
|
||||
SITE_ID = 1
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n
|
||||
USE_I18N = True
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n
|
||||
USE_L10N = True
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
|
||||
USE_TZ = True
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#locale-paths
|
||||
LOCALE_PATHS = [ROOT_DIR.path("locale")]
|
||||
|
||||
# DATABASES
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#databases
|
||||
DATABASES = {"default": env.db("DATABASE_URL")}
|
||||
DATABASES["default"]["ATOMIC_REQUESTS"] = True
|
||||
DATABASES['default']['CONN_MAX_AGE'] = 0
|
||||
|
||||
# URLS
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf
|
||||
ROOT_URLCONF = "config.urls"
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application
|
||||
WSGI_APPLICATION = "config.wsgi.application"
|
||||
|
||||
# APPS
|
||||
# ------------------------------------------------------------------------------
|
||||
DJANGO_APPS = [
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.sites",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
# "django.contrib.humanize", # Handy template tags
|
||||
"django.contrib.admin",
|
||||
]
|
||||
THIRD_PARTY_APPS = [
|
||||
"crispy_forms",
|
||||
"allauth",
|
||||
"allauth.account",
|
||||
"allauth.socialaccount",
|
||||
"allauth.socialaccount.providers.discord",
|
||||
"rest_framework",
|
||||
"rest_framework.authtoken",
|
||||
]
|
||||
|
||||
LOCAL_APPS = [
|
||||
"geeksbot_v2.users.apps.UsersConfig",
|
||||
"geeksbot_v2.guilds.apps.GuildsConfig",
|
||||
"geeksbot_v2.dmessages.apps.MessagesConfig",
|
||||
"geeksbot_v2.patreon.apps.PatreonConfig",
|
||||
"geeksbot_v2.rcon.apps.RconConfig",
|
||||
"geeksbot_v2.channels.apps.ChannelsConfig",
|
||||
# Your stuff: custom apps go here
|
||||
]
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
||||
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
||||
|
||||
# MIGRATIONS
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#migration-modules
|
||||
MIGRATION_MODULES = {"sites": "geeksbot_v2.contrib.sites.migrations"}
|
||||
|
||||
# AUTHENTICATION
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#authentication-backends
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
"django.contrib.auth.backends.ModelBackend",
|
||||
"allauth.account.auth_backends.AuthenticationBackend",
|
||||
]
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model
|
||||
AUTH_USER_MODEL = "users.User"
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url
|
||||
LOGIN_REDIRECT_URL = "users:redirect"
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#login-url
|
||||
LOGIN_URL = "account_login"
|
||||
|
||||
# PASSWORDS
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers
|
||||
PASSWORD_HASHERS = [
|
||||
# https://docs.djangoproject.com/en/dev/topics/auth/passwords/#using-argon2-with-django
|
||||
"django.contrib.auth.hashers.Argon2PasswordHasher",
|
||||
"django.contrib.auth.hashers.PBKDF2PasswordHasher",
|
||||
"django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
|
||||
"django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
|
||||
]
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"
|
||||
},
|
||||
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
|
||||
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
|
||||
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
|
||||
]
|
||||
|
||||
# MIDDLEWARE
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#middleware
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.locale.LocaleMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
]
|
||||
|
||||
# STATIC
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#static-root
|
||||
STATIC_ROOT = str(ROOT_DIR("staticfiles"))
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#static-url
|
||||
STATIC_URL = "/static/"
|
||||
# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
|
||||
STATICFILES_DIRS = [str(APPS_DIR.path("static"))]
|
||||
# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders
|
||||
STATICFILES_FINDERS = [
|
||||
"django.contrib.staticfiles.finders.FileSystemFinder",
|
||||
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
|
||||
]
|
||||
|
||||
# MEDIA
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#media-root
|
||||
MEDIA_ROOT = str(APPS_DIR("media"))
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#media-url
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
# TEMPLATES
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#templates
|
||||
TEMPLATES = [
|
||||
{
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
|
||||
"DIRS": [str(APPS_DIR.path("templates"))],
|
||||
"OPTIONS": {
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
|
||||
# https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types
|
||||
"loaders": [
|
||||
"django.template.loaders.filesystem.Loader",
|
||||
"django.template.loaders.app_directories.Loader",
|
||||
],
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.template.context_processors.i18n",
|
||||
"django.template.context_processors.media",
|
||||
"django.template.context_processors.static",
|
||||
"django.template.context_processors.tz",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
],
|
||||
},
|
||||
}
|
||||
]
|
||||
# http://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs
|
||||
CRISPY_TEMPLATE_PACK = "bootstrap4"
|
||||
|
||||
# FIXTURES
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#fixture-dirs
|
||||
FIXTURE_DIRS = (str(APPS_DIR.path("fixtures")),)
|
||||
|
||||
# SECURITY
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-httponly
|
||||
SESSION_COOKIE_HTTPONLY = True
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-httponly
|
||||
CSRF_COOKIE_HTTPONLY = True
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-browser-xss-filter
|
||||
SECURE_BROWSER_XSS_FILTER = True
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#x-frame-options
|
||||
X_FRAME_OPTIONS = "DENY"
|
||||
|
||||
# EMAIL
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
|
||||
EMAIL_BACKEND = env(
|
||||
"DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.smtp.EmailBackend"
|
||||
)
|
||||
# https://docs.djangoproject.com/en/2.2/ref/settings/#email-timeout
|
||||
EMAIL_TIMEOUT = 5
|
||||
|
||||
# ADMIN
|
||||
# ------------------------------------------------------------------------------
|
||||
# Django Admin URL.
|
||||
ADMIN_URL = "admin/"
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#admins
|
||||
ADMINS = [("""Dustin Pianalto""", "dusty.p@geeksbot.app")]
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#managers
|
||||
MANAGERS = ADMINS
|
||||
|
||||
# LOGGING
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#logging
|
||||
# See https://docs.djangoproject.com/en/dev/topics/logging for
|
||||
# more details on how to customize your logging configuration.
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"formatters": {
|
||||
"verbose": {
|
||||
"format": "%(levelname)s %(asctime)s %(module)s "
|
||||
"%(process)d %(thread)d %(message)s"
|
||||
}
|
||||
},
|
||||
"handlers": {
|
||||
"console": {
|
||||
"level": "DEBUG",
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "verbose",
|
||||
}
|
||||
},
|
||||
"root": {"level": "INFO", "handlers": ["console"]},
|
||||
}
|
||||
|
||||
# django-allauth
|
||||
# ------------------------------------------------------------------------------
|
||||
ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", False)
|
||||
SOCIAL_ACCOUNT_ALLOW_REGISTRATION = env.bool('DJANGO_SOCIAL_ACCOUNT_ALLOW_REGISTRATION', True)
|
||||
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
||||
ACCOUNT_AUTHENTICATION_METHOD = "username"
|
||||
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
||||
ACCOUNT_EMAIL_REQUIRED = False
|
||||
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
||||
ACCOUNT_EMAIL_VERIFICATION = "optional"
|
||||
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
||||
ACCOUNT_ADAPTER = "geeksbot_v2.users.adapters.AccountAdapter"
|
||||
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
||||
SOCIALACCOUNT_ADAPTER = "geeksbot_v2.users.adapters.SocialAccountAdapter"
|
||||
ACCOUNT_FORMS = {
|
||||
'signup': 'geeksbot_v2.users.forms.UserCreateForm',
|
||||
}
|
||||
|
||||
|
||||
# Your stuff...
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||
'rest_framework.authentication.TokenAuthentication',
|
||||
],
|
||||
"DEFAULT_RENDERER_CLASSES": [
|
||||
"rest_framework.renderers.JSONRenderer",
|
||||
],
|
||||
"DEFAULT_PARSER_CLASSES": [
|
||||
'rest_framework.parsers.JSONParser',
|
||||
],
|
||||
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
|
||||
"PAGE_SIZE": 100,
|
||||
}
|
||||
|
||||
SILENCED_SYSTEM_CHECKS = ["auth.W004"]
|
||||
@ -1,62 +0,0 @@
|
||||
from .base import * # noqa
|
||||
from .base import env
|
||||
|
||||
# GENERAL
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
||||
DEBUG = True
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
|
||||
SECRET_KEY = env(
|
||||
"DJANGO_SECRET_KEY",
|
||||
default="j67xnBm5ZQ7SAQwhvaMnD4WAW1EeEFVQx0KxBOyHRMYCR4LV0rVkuslZJs1rtPUE",
|
||||
)
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
|
||||
ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"]
|
||||
|
||||
# CACHES
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#caches
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
||||
"LOCATION": "",
|
||||
}
|
||||
}
|
||||
|
||||
# EMAIL
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
|
||||
EMAIL_BACKEND = env(
|
||||
"DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend"
|
||||
)
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#email-host
|
||||
EMAIL_HOST = "localhost"
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#email-port
|
||||
EMAIL_PORT = 1025
|
||||
|
||||
# django-debug-toolbar
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#prerequisites
|
||||
INSTALLED_APPS += ["debug_toolbar"] # noqa F405
|
||||
# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#middleware
|
||||
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # noqa F405
|
||||
# https://django-debug-toolbar.readthedocs.io/en/latest/configuration.html#debug-toolbar-config
|
||||
DEBUG_TOOLBAR_CONFIG = {
|
||||
"DISABLE_PANELS": ["debug_toolbar.panels.redirects.RedirectsPanel"],
|
||||
"SHOW_TEMPLATE_CONTEXT": True,
|
||||
}
|
||||
# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#internal-ips
|
||||
INTERNAL_IPS = ["127.0.0.1", "10.0.2.2"]
|
||||
if env("USE_DOCKER") == "yes":
|
||||
import socket
|
||||
|
||||
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
|
||||
INTERNAL_IPS += [ip[:-1] + "1" for ip in ips]
|
||||
|
||||
# django-extensions
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://django-extensions.readthedocs.io/en/latest/installation_instructions.html#configuration
|
||||
INSTALLED_APPS += ["django_extensions"] # noqa F405
|
||||
|
||||
# Your stuff...
|
||||
# ------------------------------------------------------------------------------
|
||||
@ -1,155 +0,0 @@
|
||||
from .base import * # noqa
|
||||
from .base import env
|
||||
|
||||
# GENERAL
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
|
||||
SECRET_KEY = env("DJANGO_SECRET_KEY")
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
|
||||
ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["geeksbot.app"])
|
||||
|
||||
# DATABASES
|
||||
# ------------------------------------------------------------------------------
|
||||
DATABASES["default"] = env.db("DATABASE_URL") # noqa F405
|
||||
DATABASES["default"]["ATOMIC_REQUESTS"] = True # noqa F405
|
||||
DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60) # noqa F405
|
||||
|
||||
# CACHES
|
||||
# ------------------------------------------------------------------------------
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": env("REDIS_URL"),
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
# Mimicing memcache behavior.
|
||||
# http://niwinz.github.io/django-redis/latest/#_memcached_exceptions_behavior
|
||||
"IGNORE_EXCEPTIONS": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
# SECURITY
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header
|
||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-ssl-redirect
|
||||
SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=True)
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-secure
|
||||
SESSION_COOKIE_SECURE = True
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-secure
|
||||
CSRF_COOKIE_SECURE = True
|
||||
# https://docs.djangoproject.com/en/dev/topics/security/#ssl-https
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-seconds
|
||||
# TODO: set this to 60 seconds first and then to 518400 once you prove the former works
|
||||
SECURE_HSTS_SECONDS = 60
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-include-subdomains
|
||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool(
|
||||
"DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True
|
||||
)
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-preload
|
||||
SECURE_HSTS_PRELOAD = env.bool("DJANGO_SECURE_HSTS_PRELOAD", default=True)
|
||||
# https://docs.djangoproject.com/en/dev/ref/middleware/#x-content-type-options-nosniff
|
||||
SECURE_CONTENT_TYPE_NOSNIFF = env.bool(
|
||||
"DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True
|
||||
)
|
||||
|
||||
# MEDIA
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# TEMPLATES
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#templates
|
||||
TEMPLATES[0]["OPTIONS"]["loaders"] = [ # noqa F405
|
||||
(
|
||||
"django.template.loaders.cached.Loader",
|
||||
[
|
||||
"django.template.loaders.filesystem.Loader",
|
||||
"django.template.loaders.app_directories.Loader",
|
||||
],
|
||||
)
|
||||
]
|
||||
|
||||
# EMAIL
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email
|
||||
DEFAULT_FROM_EMAIL = env(
|
||||
"DJANGO_DEFAULT_FROM_EMAIL", default="geeksbot <noreply@geeksbot.app>"
|
||||
)
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#server-email
|
||||
SERVER_EMAIL = env("DJANGO_SERVER_EMAIL", default=DEFAULT_FROM_EMAIL)
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix
|
||||
EMAIL_SUBJECT_PREFIX = env(
|
||||
"DJANGO_EMAIL_SUBJECT_PREFIX", default="[geeksbot]"
|
||||
)
|
||||
|
||||
# ADMIN
|
||||
# ------------------------------------------------------------------------------
|
||||
# Django Admin URL regex.
|
||||
ADMIN_URL = env("DJANGO_ADMIN_URL")
|
||||
|
||||
# Anymail (Mailgun)
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://anymail.readthedocs.io/en/stable/installation/#installing-anymail
|
||||
INSTALLED_APPS += ["anymail"] # noqa F405
|
||||
EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend"
|
||||
# https://anymail.readthedocs.io/en/stable/installation/#anymail-settings-reference
|
||||
ANYMAIL = {
|
||||
"MAILGUN_API_KEY": env("MAILGUN_API_KEY"),
|
||||
"MAILGUN_SENDER_DOMAIN": env("MAILGUN_DOMAIN"),
|
||||
"MAILGUN_API_URL": env("MAILGUN_API_URL", default="https://api.mailgun.net/v3"),
|
||||
}
|
||||
|
||||
# Collectfast
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://github.com/antonagestam/collectfast#installation
|
||||
INSTALLED_APPS = ["collectfast"] + INSTALLED_APPS # noqa F405
|
||||
AWS_PRELOAD_METADATA = True
|
||||
|
||||
# LOGGING
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#logging
|
||||
# See https://docs.djangoproject.com/en/dev/topics/logging for
|
||||
# more details on how to customize your logging configuration.
|
||||
# A sample logging configuration. The only tangible logging
|
||||
# performed by this configuration is to send an email to
|
||||
# the site admins on every HTTP 500 error when DEBUG=False.
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}},
|
||||
"formatters": {
|
||||
"verbose": {
|
||||
"format": "%(levelname)s %(asctime)s %(module)s "
|
||||
"%(process)d %(thread)d %(message)s"
|
||||
}
|
||||
},
|
||||
"handlers": {
|
||||
"mail_admins": {
|
||||
"level": "ERROR",
|
||||
"filters": ["require_debug_false"],
|
||||
"class": "django.utils.log.AdminEmailHandler",
|
||||
},
|
||||
"console": {
|
||||
"level": "DEBUG",
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "verbose",
|
||||
},
|
||||
},
|
||||
"root": {"level": "INFO", "handlers": ["console"]},
|
||||
"loggers": {
|
||||
"django.request": {
|
||||
"handlers": ["mail_admins"],
|
||||
"level": "ERROR",
|
||||
"propagate": True,
|
||||
},
|
||||
"django.security.DisallowedHost": {
|
||||
"level": "ERROR",
|
||||
"handlers": ["console", "mail_admins"],
|
||||
"propagate": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# Your stuff...
|
||||
# ------------------------------------------------------------------------------
|
||||
@ -1,53 +0,0 @@
|
||||
"""
|
||||
With these settings, tests run faster.
|
||||
"""
|
||||
|
||||
from .base import * # noqa
|
||||
from .base import env
|
||||
|
||||
# GENERAL
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
||||
DEBUG = False
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
|
||||
SECRET_KEY = env(
|
||||
"DJANGO_SECRET_KEY",
|
||||
default="f2z33VJdMTuS8AHpyHr1p2AAR9daYVQFMLEqBGxEP2aZLmtBpRVudyOrhK1DL9Ov",
|
||||
)
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#test-runner
|
||||
TEST_RUNNER = "django.test.runner.DiscoverRunner"
|
||||
|
||||
# CACHES
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#caches
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
||||
"LOCATION": "",
|
||||
}
|
||||
}
|
||||
|
||||
# PASSWORDS
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers
|
||||
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
|
||||
|
||||
# TEMPLATES
|
||||
# ------------------------------------------------------------------------------
|
||||
TEMPLATES[0]["OPTIONS"]["loaders"] = [ # noqa F405
|
||||
(
|
||||
"django.template.loaders.cached.Loader",
|
||||
[
|
||||
"django.template.loaders.filesystem.Loader",
|
||||
"django.template.loaders.app_directories.Loader",
|
||||
],
|
||||
)
|
||||
]
|
||||
|
||||
# EMAIL
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
|
||||
EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
|
||||
|
||||
# Your stuff...
|
||||
# ------------------------------------------------------------------------------
|
||||
@ -1,50 +0,0 @@
|
||||
from django.conf import settings
|
||||
from django.urls import include, path
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.views.generic import TemplateView
|
||||
from django.views import defaults as default_views
|
||||
|
||||
urlpatterns = [
|
||||
path("", TemplateView.as_view(template_name="pages/home.html"), name="home"),
|
||||
path(
|
||||
"about/", TemplateView.as_view(template_name="pages/about.html"), name="about"
|
||||
),
|
||||
# Django Admin, use {% url 'admin:index' %}
|
||||
path(settings.ADMIN_URL, admin.site.urls),
|
||||
# User management
|
||||
path("users/", include("geeksbot_v2.users.urls", namespace="users")),
|
||||
path("accounts/", include("allauth.urls")),
|
||||
# Your stuff: custom urls includes go here
|
||||
path("api/users/", include("geeksbot_v2.users.api_urls", namespace="users_api")),
|
||||
path("api/guilds/", include("geeksbot_v2.guilds.api_urls", namespace="guilds_api")),
|
||||
path("api/channels/", include("geeksbot_v2.channels.api_urls", namespace="channels_api")),
|
||||
path("api/messages/", include("geeksbot_v2.dmessages.api_urls", namespace="messages_api")),
|
||||
path("api/rcon/", include("geeksbot_v2.rcon.api_urls", namespace="rcon_api")),
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
if settings.DEBUG:
|
||||
# This allows the error pages to be debugged during development, just visit
|
||||
# these url in browser to see how these error pages look like.
|
||||
urlpatterns += [
|
||||
path(
|
||||
"400/",
|
||||
default_views.bad_request,
|
||||
kwargs={"exception": Exception("Bad Request!")},
|
||||
),
|
||||
path(
|
||||
"403/",
|
||||
default_views.permission_denied,
|
||||
kwargs={"exception": Exception("Permission Denied")},
|
||||
),
|
||||
path(
|
||||
"404/",
|
||||
default_views.page_not_found,
|
||||
kwargs={"exception": Exception("Page not Found")},
|
||||
),
|
||||
path("500/", default_views.server_error),
|
||||
]
|
||||
if "debug_toolbar" in settings.INSTALLED_APPS:
|
||||
import debug_toolbar
|
||||
|
||||
urlpatterns = [path("__debug__/", include(debug_toolbar.urls))] + urlpatterns
|
||||
@ -1,39 +0,0 @@
|
||||
"""
|
||||
WSGI config for geeksbot project.
|
||||
|
||||
This module contains the WSGI application used by Django's development server
|
||||
and any production WSGI deployments. It should expose a module-level variable
|
||||
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
|
||||
this application via the ``WSGI_APPLICATION`` setting.
|
||||
|
||||
Usually you will have the standard Django WSGI application here, but it also
|
||||
might make sense to replace the whole Django WSGI application with a custom one
|
||||
that later delegates to the Django one. For example, you could introduce WSGI
|
||||
middleware here, or combine a Django application with an application of another
|
||||
framework.
|
||||
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
# This allows easy placement of apps within the interior
|
||||
# geeksbot_v2 directory.
|
||||
app_path = os.path.abspath(
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir)
|
||||
)
|
||||
sys.path.append(os.path.join(app_path, "geeksbot_v2"))
|
||||
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
|
||||
# if running multiple sites in the same mod_wsgi process. To fix this, use
|
||||
# mod_wsgi daemon mode with each site in its own daemon process, or use
|
||||
# os.environ["DJANGO_SETTINGS_MODULE"] = "config.settings.production"
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production")
|
||||
|
||||
# This application object is used by any WSGI server configured to use this
|
||||
# file. This includes Django's development server, if the WSGI_APPLICATION
|
||||
# setting points here.
|
||||
application = get_wsgi_application()
|
||||
# Apply WSGI middleware here.
|
||||
# from helloworld.wsgi import HelloWorldApplication
|
||||
# application = HelloWorldApplication(application)
|
||||
@ -1,20 +0,0 @@
|
||||
import pytest
|
||||
from django.conf import settings
|
||||
from django.test import RequestFactory
|
||||
|
||||
from geeksbot_v2.users.tests.factories import UserFactory
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def media_storage(settings, tmpdir):
|
||||
settings.MEDIA_ROOT = tmpdir.strpath
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def user() -> settings.AUTH_USER_MODEL:
|
||||
return UserFactory()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def request_factory() -> RequestFactory:
|
||||
return RequestFactory()
|
||||
@ -1,5 +0,0 @@
|
||||
"""
|
||||
To understand why this file is here, please read:
|
||||
|
||||
http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django
|
||||
"""
|
||||
@ -1,5 +0,0 @@
|
||||
"""
|
||||
To understand why this file is here, please read:
|
||||
|
||||
http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django
|
||||
"""
|
||||
@ -1,42 +0,0 @@
|
||||
import django.contrib.sites.models
|
||||
from django.contrib.sites.models import _simple_domain_name_validator
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Site",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
serialize=False,
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"domain",
|
||||
models.CharField(
|
||||
max_length=100,
|
||||
verbose_name="domain name",
|
||||
validators=[_simple_domain_name_validator],
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=50, verbose_name="display name")),
|
||||
],
|
||||
options={
|
||||
"ordering": ("domain",),
|
||||
"db_table": "django_site",
|
||||
"verbose_name": "site",
|
||||
"verbose_name_plural": "sites",
|
||||
},
|
||||
bases=(models.Model,),
|
||||
managers=[("objects", django.contrib.sites.models.SiteManager())],
|
||||
)
|
||||
]
|
||||
@ -1,20 +0,0 @@
|
||||
import django.contrib.sites.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("sites", "0001_initial")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="site",
|
||||
name="domain",
|
||||
field=models.CharField(
|
||||
max_length=100,
|
||||
unique=True,
|
||||
validators=[django.contrib.sites.models._simple_domain_name_validator],
|
||||
verbose_name="domain name",
|
||||
),
|
||||
)
|
||||
]
|
||||
@ -1,33 +0,0 @@
|
||||
"""
|
||||
To understand why this file is here, please read:
|
||||
http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def update_site_forward(apps, schema_editor):
|
||||
"""Set site domain and name."""
|
||||
Site = apps.get_model("sites", "Site")
|
||||
Site.objects.update_or_create(
|
||||
id=settings.SITE_ID,
|
||||
defaults={
|
||||
"domain": "geeksbot.app",
|
||||
"name": "geeksbot",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def update_site_backward(apps, schema_editor):
|
||||
"""Revert site domain and name to default."""
|
||||
Site = apps.get_model("sites", "Site")
|
||||
Site.objects.update_or_create(
|
||||
id=settings.SITE_ID, defaults={"domain": "example.com", "name": "example.com"}
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("sites", "0002_alter_domain_unique")]
|
||||
|
||||
operations = [migrations.RunPython(update_site_forward, update_site_backward)]
|
||||
@ -1,5 +0,0 @@
|
||||
"""
|
||||
To understand why this file is here, please read:
|
||||
|
||||
http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django
|
||||
"""
|
||||
@ -1,10 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Message
|
||||
from .models import GuildInfo
|
||||
from .models import AdminRequest
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(Message)
|
||||
admin.site.register(GuildInfo)
|
||||
admin.site.register(AdminRequest)
|
||||
@ -1,21 +0,0 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import MessageDetailAPI, MessagesAPI
|
||||
from .views import RequestDetailAPI, RequestsAPI
|
||||
from .views import CommentDetailAPI, CommentsAPI, CommentsCountAPI
|
||||
from .views import WaitForMessageAPI
|
||||
from .views import UserRequestsAPI
|
||||
|
||||
app_name = "messages_api"
|
||||
urlpatterns = [
|
||||
path("", view=MessagesAPI.as_view(), name="message_list"),
|
||||
path("<str:id>/", view=MessageDetailAPI.as_view(), name='message_detail'),
|
||||
path("<str:guild_id>/requests/", view=RequestsAPI.as_view(), name="requests_list"),
|
||||
path("<str:guild_id>/requests/<str:request_id>/", view=RequestDetailAPI.as_view(), name='request_detail'),
|
||||
path("<str:guild_id>/requests/<str:request_id>/comments/", view=CommentsAPI.as_view(), name="comments_list"),
|
||||
path("<str:guild_id>/requests/<str:request_id>/comments/count/", view=CommentsCountAPI.as_view(), name="comments_count"),
|
||||
path("<str:guild_id>/requests/<str:request_id>/comments/<str:comment_id>/", view=CommentDetailAPI.as_view(), name='comment_detail'),
|
||||
path("<str:guild_id>/requests/user/<str:author_id>/", view=UserRequestsAPI.as_view(), name='user_requests_list'),
|
||||
path("<str:id>/wait/", view=WaitForMessageAPI.as_view(), name='wait_for_message'),
|
||||
path("<str:id>/wait/<int:timeout>/", view=WaitForMessageAPI.as_view(), name='wait_for_message_timeout'),
|
||||
]
|
||||
@ -1,7 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class MessagesConfig(AppConfig):
|
||||
name = 'geeksbot_v2.dmessages'
|
||||
verbose_name = _("DMessages")
|
||||
@ -1,59 +0,0 @@
|
||||
# Generated by Django 2.2.4 on 2019-09-20 21:39
|
||||
|
||||
import django.contrib.postgres.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AdminComment',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('content', models.CharField(max_length=1000)),
|
||||
('updated_at', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AdminRequest',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('completed', models.BooleanField(default=False)),
|
||||
('requested_at', models.DateTimeField(auto_now_add=True)),
|
||||
('completed_at', models.DateTimeField(blank=True, default=None, null=True)),
|
||||
('completed_message', models.CharField(blank=True, default=None, max_length=1000, null=True)),
|
||||
('content', models.CharField(max_length=2000)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='GuildInfo',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('type', models.PositiveSmallIntegerField()),
|
||||
('text', models.TextField(max_length=1980)),
|
||||
('format', models.PositiveSmallIntegerField()),
|
||||
('channel', models.CharField(max_length=30)),
|
||||
('message_number', models.PositiveSmallIntegerField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Message',
|
||||
fields=[
|
||||
('id', models.CharField(max_length=30, primary_key=True, serialize=False)),
|
||||
('created_at', models.DateTimeField()),
|
||||
('modified_at', models.DateTimeField(blank=True, null=True)),
|
||||
('deleted_at', models.DateTimeField(blank=True, null=True)),
|
||||
('content', models.CharField(max_length=2000)),
|
||||
('previous_content', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=2000), default=list, size=None)),
|
||||
('tagged_everyone', models.BooleanField()),
|
||||
('embeds', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None)),
|
||||
('previous_embeds', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None), default=list, size=None)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@ -1,95 +0,0 @@
|
||||
# Generated by Django 2.2.4 on 2019-09-20 21:39
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('guilds', '0001_initial'),
|
||||
('dmessages', '0001_initial'),
|
||||
('channels', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='message',
|
||||
name='author',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='message',
|
||||
name='channel',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='channels.Channel'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='message',
|
||||
name='guild',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='message',
|
||||
name='tagged_channels',
|
||||
field=models.ManyToManyField(related_name='_message_tagged_channels_+', to='channels.Channel'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='message',
|
||||
name='tagged_roles',
|
||||
field=models.ManyToManyField(to='guilds.Role'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='message',
|
||||
name='tagged_users',
|
||||
field=models.ManyToManyField(related_name='_message_tagged_users_+', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='guildinfo',
|
||||
name='guild',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='guildinfo',
|
||||
name='message',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dmessages.Message'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='adminrequest',
|
||||
name='author',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='adminrequest',
|
||||
name='channel',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='channels.Channel'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='adminrequest',
|
||||
name='completed_by',
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='adminrequest',
|
||||
name='guild',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='adminrequest',
|
||||
name='message',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='dmessages.Message'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='admincomment',
|
||||
name='author',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='admincomment',
|
||||
name='request',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dmessages.AdminRequest'),
|
||||
),
|
||||
]
|
||||
@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.2.4 on 2019-09-21 07:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dmessages', '0002_auto_20190920_2139'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='message',
|
||||
name='content',
|
||||
field=models.CharField(blank=True, max_length=2000, null=True),
|
||||
),
|
||||
]
|
||||
@ -1,291 +0,0 @@
|
||||
from datetime import datetime
|
||||
|
||||
from django.db import models
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from rest_framework import status
|
||||
|
||||
from geeksbot_v2.guilds.models import Guild
|
||||
from geeksbot_v2.guilds.models import Role
|
||||
from geeksbot_v2.users.models import User
|
||||
from geeksbot_v2.channels.models import Channel
|
||||
from .utils import create_error_response
|
||||
from .utils import create_success_response
|
||||
from .utils import create_request_success_response
|
||||
from .utils import create_comment_success_response
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
||||
class Message(models.Model):
|
||||
id = models.CharField(max_length=30, primary_key=True)
|
||||
author = models.ForeignKey(User, related_name="+", on_delete=models.CASCADE)
|
||||
guild = models.ForeignKey(Guild, on_delete=models.CASCADE)
|
||||
channel = models.ForeignKey(Channel, related_name="+", on_delete=models.CASCADE)
|
||||
created_at = models.DateTimeField()
|
||||
modified_at = models.DateTimeField(null=True, blank=True)
|
||||
deleted_at = models.DateTimeField(null=True, blank=True)
|
||||
content = models.CharField(max_length=2000, null=True, blank=True)
|
||||
previous_content = ArrayField(models.CharField(max_length=2000), default=list)
|
||||
tagged_users = models.ManyToManyField(User, related_name="+")
|
||||
tagged_channels = models.ManyToManyField(Channel, related_name="+")
|
||||
tagged_roles = models.ManyToManyField(Role)
|
||||
tagged_everyone = models.BooleanField()
|
||||
embeds = ArrayField(models.TextField(), default=list)
|
||||
previous_embeds = ArrayField(ArrayField(models.TextField()), default=list)
|
||||
|
||||
@classmethod
|
||||
def add_new_message(cls, data):
|
||||
id = data.get('id')
|
||||
if id and cls.get_message_by_id(id):
|
||||
return create_error_response("Message Already Exists",
|
||||
status=status.HTTP_409_CONFLICT)
|
||||
author_id = data.get('author')
|
||||
guild_id = data.get('guild')
|
||||
channel_id = data.get('channel')
|
||||
created_at = data.get('created_at')
|
||||
content = data.get('content')
|
||||
tagged_everyone = data.get('tagged_everyone')
|
||||
if not (id and author_id and guild_id and channel_id and created_at and (tagged_everyone is not None)):
|
||||
return create_error_response("One or more required fields are missing.",
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
author = User.get_user_by_id(author_id)
|
||||
if not isinstance(author, User):
|
||||
return create_error_response("Author Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
guild = Guild.get_guild_by_id(guild_id)
|
||||
if not isinstance(guild, Guild):
|
||||
return create_error_response("Guild Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
channel = Channel.get_channel_by_id(guild_id, channel_id)
|
||||
if not isinstance(channel, Channel):
|
||||
return create_error_response("Channel Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
created_at = datetime.fromtimestamp(created_at)
|
||||
|
||||
message = cls(
|
||||
id=id,
|
||||
author=author,
|
||||
guild=guild,
|
||||
channel=channel,
|
||||
created_at=created_at,
|
||||
tagged_everyone=tagged_everyone or False,
|
||||
content=content or '',
|
||||
embeds=data.get('embeds') or []
|
||||
)
|
||||
message.save()
|
||||
if data.get('tagged_users'):
|
||||
tagged_users = data.get('tagged_users')
|
||||
for user_id in tagged_users:
|
||||
user = User.get_user_by_id(user_id)
|
||||
if user:
|
||||
message.tagged_users.add(user)
|
||||
if data.get('tagged_roles'):
|
||||
tagged_roles = data.get('tagged_roles')
|
||||
for role_id in tagged_roles:
|
||||
role = Role.get_role_by_id(role_id)
|
||||
if role:
|
||||
message.tagged_roles.add(role)
|
||||
if data.get('tagged_channels'):
|
||||
tagged_channels = data.get('tagged_channels')
|
||||
for channel_id in tagged_channels:
|
||||
channel = Channel.get_channel_by_id(guild_id, channel_id)
|
||||
if channel:
|
||||
message.tagged_channels.add(channel)
|
||||
|
||||
return create_success_response(message, status.HTTP_201_CREATED, many=False)
|
||||
|
||||
def update_message(self, data):
|
||||
if data.get('modified_at'):
|
||||
self.modified_at = datetime.fromtimestamp(int(data.get('modified_at')))
|
||||
if data.get('deleted_at'):
|
||||
self.deleted_at = datetime.fromtimestamp(int(data.get('deleted_at')))
|
||||
if data.get('content'):
|
||||
content = data.get('content')
|
||||
if content != self.content:
|
||||
self.previous_content.append(self.content)
|
||||
self.content = content
|
||||
if data.get('embeds'):
|
||||
embeds = data.get('embeds')
|
||||
if embeds != self.embeds:
|
||||
self.previous_embeds.append(self.embeds)
|
||||
self.embeds = embeds
|
||||
if data.get('tagged_everyone'):
|
||||
tagged_everyone = data.get('tagged_everyone')
|
||||
if self.tagged_everyone or tagged_everyone:
|
||||
self.tagged_everyone = True
|
||||
if data.get('tagged_users'):
|
||||
tagged_users = data.get('tagged_users')
|
||||
for user in tagged_users:
|
||||
if user not in self.tagged_users:
|
||||
self.tagged_users.append(user)
|
||||
if data.get('tagged_roles'):
|
||||
tagged_roles = data.get('tagged_roles')
|
||||
for role in tagged_roles:
|
||||
if role not in self.tagged_roles:
|
||||
self.tagged_roles.append(role)
|
||||
if data.get('tagged_channels'):
|
||||
tagged_channels = data.get('tagged_channels')
|
||||
for channel in tagged_channels:
|
||||
if channel not in self.tagged_channels:
|
||||
self.tagged_channels.append(channel)
|
||||
|
||||
self.save()
|
||||
return create_success_response(self, status.HTTP_202_ACCEPTED, many=False)
|
||||
|
||||
@classmethod
|
||||
def get_message_by_id(cls, id):
|
||||
try:
|
||||
return cls.objects.get(id=id)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return (f'{self.created_at} | '
|
||||
f'{self.author.id}'
|
||||
f'{" | Modified" if self.modified_at else ""}'
|
||||
f'{" | Deleted" if self.deleted_at else ""}')
|
||||
|
||||
|
||||
class GuildInfo(models.Model):
|
||||
message = models.ForeignKey(
|
||||
Message, on_delete=models.CASCADE, blank=True, null=True
|
||||
)
|
||||
guild = models.ForeignKey(Guild, on_delete=models.CASCADE)
|
||||
type = models.PositiveSmallIntegerField()
|
||||
text = models.TextField(max_length=1980)
|
||||
format = models.PositiveSmallIntegerField()
|
||||
channel = models.CharField(max_length=30)
|
||||
message_number = models.PositiveSmallIntegerField()
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.guild.id} | {self.text[:25]}"
|
||||
|
||||
|
||||
class AdminRequest(models.Model):
|
||||
guild = models.ForeignKey(Guild, on_delete=models.CASCADE)
|
||||
author = models.ForeignKey(User, related_name="+", on_delete=models.DO_NOTHING)
|
||||
message = models.ForeignKey(Message, on_delete=models.DO_NOTHING)
|
||||
channel = models.ForeignKey(Channel, on_delete=models.DO_NOTHING, null=True)
|
||||
completed = models.BooleanField(default=False)
|
||||
requested_at = models.DateTimeField(auto_now_add=True, blank=True)
|
||||
completed_at = models.DateTimeField(null=True, blank=True, default=None)
|
||||
completed_by = models.ForeignKey(
|
||||
User, related_name="+", on_delete=models.DO_NOTHING, null=True, blank=True, default=None
|
||||
)
|
||||
completed_message = models.CharField(max_length=1000, null=True, blank=True, default=None)
|
||||
content = models.CharField(max_length=2000)
|
||||
|
||||
def update_request(self, data):
|
||||
completed = data.get('completed', False)
|
||||
completed_by_id = data.get('completed_by')
|
||||
completed_message = data.get('message', '')
|
||||
if not self.completed and completed:
|
||||
self.completed = completed
|
||||
self.completed_at = datetime.utcnow()
|
||||
self.completed_message = completed_message
|
||||
user = User.get_user_by_id(completed_by_id)
|
||||
if not isinstance(user, User):
|
||||
return create_error_response('User Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
self.completed_by = user
|
||||
self.save()
|
||||
return create_request_success_response(self, status.HTTP_202_ACCEPTED)
|
||||
|
||||
@classmethod
|
||||
def add_new_request(cls, guild_id, data):
|
||||
author_id = data.get('author')
|
||||
message_id = data.get('message')
|
||||
channel_id = data.get('channel')
|
||||
content = data.get('content')
|
||||
if not (guild_id and author_id and message_id and channel_id and content):
|
||||
return create_error_response("One or more of the required fields are missing.",
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
guild = Guild.get_guild_by_id(guild_id)
|
||||
if not isinstance(guild, Guild):
|
||||
return create_error_response('Guild Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
author = User.get_user_by_id(author_id)
|
||||
if not isinstance(author, User):
|
||||
return create_error_response('Author Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
message = Message.get_message_by_id(message_id)
|
||||
if not isinstance(message, Message):
|
||||
return create_error_response('Message Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
channel = Channel.get_channel_by_id(guild_id, channel_id)
|
||||
if not isinstance(channel, Channel):
|
||||
return create_error_response('Channel Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
print('test')
|
||||
|
||||
request = cls(
|
||||
guild=guild,
|
||||
author=author,
|
||||
message=message,
|
||||
channel=channel,
|
||||
content=content
|
||||
)
|
||||
request.save()
|
||||
return create_request_success_response(request, status.HTTP_201_CREATED, many=False)
|
||||
|
||||
@classmethod
|
||||
def get_open_requests_by_guild(cls, guild_id):
|
||||
return cls.objects.filter(guild__id=guild_id).filter(completed=False)
|
||||
|
||||
@classmethod
|
||||
def get_open_request_by_id(cls, guild_id, request_id):
|
||||
try:
|
||||
return cls.get_open_requests_by_guild(guild_id).get(id=request_id)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.guild.id} | {self.requested_at} | By {self.author.id}"
|
||||
|
||||
@classmethod
|
||||
def get_open_requests_by_guild_author(cls, guild_id, author_id):
|
||||
return cls.get_open_requests_by_guild(guild_id).filter(author__id=author_id)
|
||||
|
||||
|
||||
class AdminComment(models.Model):
|
||||
request = models.ForeignKey(AdminRequest, on_delete=models.CASCADE)
|
||||
author = models.ForeignKey(User, on_delete=models.DO_NOTHING)
|
||||
content = models.CharField(max_length=1000)
|
||||
updated_at = models.DateTimeField(auto_now_add=True, blank=True)
|
||||
|
||||
@classmethod
|
||||
def add_new_comment(cls, data, guild_id, request_id):
|
||||
author_id = data.get('author')
|
||||
content = data.get('content')
|
||||
if not (request_id and author_id and content):
|
||||
return create_error_response('Request, Author, and Content are required fields',
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
request = AdminRequest.get_open_request_by_id(guild_id, request_id)
|
||||
if not isinstance(request, AdminRequest):
|
||||
return create_error_response("Admin Request Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
author = User.get_user_by_id(author_id)
|
||||
if not isinstance(author, User):
|
||||
return create_error_response("Author Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
comment = cls(
|
||||
request=request,
|
||||
author=author,
|
||||
content=content
|
||||
)
|
||||
comment.save()
|
||||
return create_comment_success_response(comment, status.HTTP_201_CREATED, many=False)
|
||||
|
||||
@classmethod
|
||||
def get_comment_by_id(cls, comment_id):
|
||||
try:
|
||||
return cls.objects.get(id=comment_id)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_comments_by_request(cls, request):
|
||||
return cls.objects.filter(request=request).order_by('updated_at')
|
||||
@ -1,30 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import Message
|
||||
from .models import GuildInfo
|
||||
from .models import AdminRequest
|
||||
from .models import AdminComment
|
||||
|
||||
|
||||
class MessageSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Message
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class GuildInfoSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = GuildInfo
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class AdminRequestSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AdminRequest
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class AdminCommentSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AdminComment
|
||||
fields = "__all__"
|
||||
@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@ -1,28 +0,0 @@
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
||||
|
||||
def create_error_response(msg, status=status.HTTP_404_NOT_FOUND):
|
||||
return Response({'details': msg},
|
||||
status=status)
|
||||
|
||||
|
||||
def create_success_response(message_data, status, many: bool = False):
|
||||
from .serializers import MessageSerializer
|
||||
|
||||
return Response(MessageSerializer(message_data, many=many).data,
|
||||
status=status)
|
||||
|
||||
|
||||
def create_request_success_response(request_data, status, many: bool = False):
|
||||
from .serializers import AdminRequestSerializer
|
||||
|
||||
return Response(AdminRequestSerializer(request_data, many=many).data,
|
||||
status=status)
|
||||
|
||||
|
||||
def create_comment_success_response(comment_data, status, many: bool = False):
|
||||
from .serializers import AdminCommentSerializer
|
||||
|
||||
return Response(AdminCommentSerializer(comment_data, many=many).data,
|
||||
status=status)
|
||||
@ -1,180 +0,0 @@
|
||||
from time import sleep
|
||||
from datetime import datetime
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import status
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
||||
|
||||
from .models import Message
|
||||
from .models import AdminComment
|
||||
from .models import AdminRequest
|
||||
from .models import GuildInfo
|
||||
from geeksbot_v2.utils.api_utils import PaginatedAPIView
|
||||
from .utils import create_error_response
|
||||
from .utils import create_success_response
|
||||
from .utils import create_request_success_response
|
||||
from .utils import create_comment_success_response
|
||||
from .serializers import AdminRequestSerializer
|
||||
from .serializers import AdminCommentSerializer
|
||||
|
||||
# Create your views here.
|
||||
|
||||
# API Views
|
||||
|
||||
|
||||
class MessagesAPI(PaginatedAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, format=None):
|
||||
messages = Message.objects.all()
|
||||
page = self.paginate_queryset(messages)
|
||||
if page:
|
||||
return create_success_response(page, status.HTTP_200_OK, many=True)
|
||||
return create_success_response(messages, status.HTTP_200_OK, many=True)
|
||||
|
||||
def post(self, request, format=None):
|
||||
data = dict(request.data)
|
||||
return Message.add_new_message(data)
|
||||
|
||||
|
||||
class MessageDetailAPI(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, id, format=None):
|
||||
message = Message.get_message_by_id(id)
|
||||
if message:
|
||||
return create_success_response(message, status.HTTP_200_OK, many=False)
|
||||
else:
|
||||
return create_error_response("Message Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def put(self, request, id, format=None):
|
||||
data = dict(request.data)
|
||||
message = Message.get_message_by_id(id)
|
||||
if message:
|
||||
return message.update_message(data)
|
||||
else:
|
||||
return create_error_response('Message Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
class WaitForMessageAPI(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, id, timeout: int = 3, format=None):
|
||||
message = Message.get_message_by_id(id)
|
||||
try_count = 0
|
||||
while not message:
|
||||
sleep(0.1)
|
||||
try_count += 1
|
||||
if try_count > timeout * 10:
|
||||
return create_error_response("Timeout reached before message is available.",
|
||||
statu=status.HTTP_404_NOT_FOUND)
|
||||
message = Message.get_message_by_id(id)
|
||||
return create_success_response(message, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class RequestsAPI(PaginatedAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, guild_id, format=None):
|
||||
requests = AdminRequest.get_open_requests_by_guild(guild_id)
|
||||
page = self.paginate_queryset(requests)
|
||||
if page is not None:
|
||||
return create_request_success_response(page, status.HTTP_200_OK, many=True)
|
||||
if requests:
|
||||
return create_request_success_response(requests, status.HTTP_200_OK, many=True)
|
||||
return create_error_response("No requests found")
|
||||
|
||||
def post(self, request, guild_id, format=None):
|
||||
data = dict(request.data)
|
||||
return AdminRequest.add_new_request(guild_id, data)
|
||||
|
||||
|
||||
class UserRequestsAPI(PaginatedAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, guild_id, author_id, format=None):
|
||||
requests = AdminRequest.get_open_requests_by_guild_author(guild_id, author_id)
|
||||
page = self.paginate_queryset(requests)
|
||||
if page is not None:
|
||||
return create_request_success_response(page, status.HTTP_200_OK, many=True)
|
||||
if requests:
|
||||
return create_request_success_response(requests, status.HTTP_200_OK, many=True)
|
||||
return create_error_response("No requests found")
|
||||
|
||||
|
||||
class RequestDetailAPI(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, req, guild_id, request_id, format=None):
|
||||
req = AdminRequest.get_open_request_by_id(guild_id, request_id)
|
||||
if req:
|
||||
comments = AdminComment.get_comments_by_request(req)
|
||||
if comments:
|
||||
data = AdminRequestSerializer(req).data
|
||||
data['comments'] = AdminCommentSerializer(comments, many=True).data
|
||||
return Response(data, status.HTTP_200_OK)
|
||||
else:
|
||||
return create_request_success_response(req, status.HTTP_200_OK, many=False)
|
||||
else:
|
||||
return create_error_response("That Request Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def put(self, request, guild_id, request_id, format=None):
|
||||
req = AdminRequest.get_open_request_by_id(guild_id, request_id)
|
||||
if req:
|
||||
data = dict(request.data)
|
||||
return req.update_request(data)
|
||||
return create_error_response("That Request Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def delete(self, request, guild_id, request_id, format=None):
|
||||
data = dict(request.data)
|
||||
request = AdminRequest.get_open_request_by_id(guild_id, request_id)
|
||||
data['completed'] = True
|
||||
data['completed_at'] = datetime.utcnow()
|
||||
return request.update_request(data)
|
||||
|
||||
|
||||
class CommentsAPI(PaginatedAPIView):
|
||||
permissions_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, guild_id, request_id, format=None):
|
||||
comments = AdminComment.get_comments_by_request(request_id)
|
||||
if comments:
|
||||
return create_comment_success_response(comments, status=status.HTTP_200_OK, many=True)
|
||||
return create_error_response("No Comments found")
|
||||
|
||||
def post(self, request, guild_id, request_id, format=None):
|
||||
data = dict(request.data)
|
||||
return AdminComment.add_new_comment(data, guild_id, request_id)
|
||||
|
||||
|
||||
class CommentsCountAPI(PaginatedAPIView):
|
||||
permissions_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, guild_id, request_id, format=None):
|
||||
comments = AdminComment.get_comments_by_request(request_id)
|
||||
if comments:
|
||||
return Response(len(comments), status=status.HTTP_200_OK)
|
||||
return Response(0, status.HTTP_200_OK)
|
||||
|
||||
|
||||
class CommentDetailAPI(APIView):
|
||||
permissions_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, request_id, comment_id, format=None):
|
||||
comment = AdminComment.get_comment_by_id(comment_id)
|
||||
if comment:
|
||||
if comment.request.id != request_id:
|
||||
return create_error_response("That comment is not for this request",
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
return create_comment_success_response(comment, status.HTTP_200_OK, many=False)
|
||||
else:
|
||||
return create_error_response("Comment Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
@ -1,42 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
set -o nounset
|
||||
|
||||
if [ -z "${POSTGRES_USER}" ]; then
|
||||
base_postgres_image_default_user='postgres'
|
||||
export POSTGRES_USER="${base_postgres_image_default_user}"
|
||||
fi
|
||||
|
||||
postgres_ready() {
|
||||
python << END
|
||||
import sys
|
||||
|
||||
import psycopg2
|
||||
|
||||
try:
|
||||
psycopg2.connect(
|
||||
dbname="${POSTGRES_DB}",
|
||||
user="${POSTGRES_USER}",
|
||||
password="${POSTGRES_PASSWORD}",
|
||||
host="${POSTGRES_HOST}",
|
||||
port="${POSTGRES_PORT}",
|
||||
)
|
||||
except psycopg2.OperationalError:
|
||||
sys.exit(-1)
|
||||
sys.exit(0)
|
||||
|
||||
END
|
||||
}
|
||||
until postgres_ready; do
|
||||
>&2 echo 'Waiting for PostgreSQL to become available...'
|
||||
sleep 1
|
||||
done
|
||||
>&2 echo 'PostgreSQL is available'
|
||||
|
||||
python manage.py collectstatic --noinput
|
||||
python manage.py makemigrations --noinput
|
||||
python manage.py migrate
|
||||
|
||||
/usr/bin/supervisord -c /etc/supervisor/supervisord.conf
|
||||
@ -1,8 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from geeksbot_v2.guilds.models import Guild
|
||||
from geeksbot_v2.guilds.models import Role
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(Guild)
|
||||
admin.site.register(Role)
|
||||
@ -1,14 +0,0 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import GuildsAPI, GuildDetail
|
||||
from .views import RolesAPI, RoleDetailAPI
|
||||
from .views import AdminRolesAPI
|
||||
|
||||
app_name = "guilds_api"
|
||||
urlpatterns = [
|
||||
path("", view=GuildsAPI.as_view(), name="list"),
|
||||
path("<str:id>/", view=GuildDetail.as_view(), name='detail'),
|
||||
path("<str:guild_id>/roles/", view=RolesAPI.as_view(), name="list"),
|
||||
path("<str:guild_id>/roles/admin/", view=AdminRolesAPI.as_view(), name='admin'),
|
||||
path("<str:guild_id>/roles/<str:id>/", view=RoleDetailAPI.as_view(), name='detail'),
|
||||
]
|
||||
@ -1,7 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class GuildsConfig(AppConfig):
|
||||
name = 'geeksbot_v2.guilds'
|
||||
verbose_name = _("Guilds")
|
||||
@ -1,35 +0,0 @@
|
||||
# Generated by Django 2.2.4 on 2019-09-20 21:39
|
||||
|
||||
import django.contrib.postgres.fields
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Guild',
|
||||
fields=[
|
||||
('id', models.CharField(max_length=30, primary_key=True, serialize=False)),
|
||||
('admin_chat', models.CharField(blank=True, max_length=30, null=True)),
|
||||
('new_patron_message', models.TextField(blank=True, max_length=1000, null=True)),
|
||||
('default_channel', models.CharField(max_length=30)),
|
||||
('new_patron_channel', models.CharField(blank=True, max_length=30, null=True)),
|
||||
('prefixes', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=10), size=None)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Role',
|
||||
fields=[
|
||||
('id', models.CharField(max_length=30, primary_key=True, serialize=False)),
|
||||
('role_type', models.PositiveSmallIntegerField()),
|
||||
('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@ -1,25 +0,0 @@
|
||||
# Generated by Django 2.2.4 on 2019-09-21 02:50
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('guilds', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='guild',
|
||||
name='admin_chat',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='guild',
|
||||
name='default_channel',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='guild',
|
||||
name='new_patron_channel',
|
||||
),
|
||||
]
|
||||
@ -1,133 +0,0 @@
|
||||
import os
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from rest_framework import status
|
||||
|
||||
from .utils import create_error_response
|
||||
from .utils import create_success_response
|
||||
from .utils import create_role_success_response
|
||||
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
||||
class Guild(models.Model):
|
||||
id = models.CharField(max_length=30, primary_key=True)
|
||||
new_patron_message = models.TextField(max_length=1000, blank=True, null=True)
|
||||
prefixes = ArrayField(models.CharField(max_length=10))
|
||||
|
||||
def __str__(self):
|
||||
return self.id
|
||||
|
||||
def update_guild(self, data):
|
||||
if data.get('new_patron_message'):
|
||||
self.new_patron_message = data.get('new_patron_message')
|
||||
if data.get('add_prefix'):
|
||||
if data.get('add_prefix') not in self.prefixes:
|
||||
self.prefixes.append(data.get('add_prefix'))
|
||||
if data.get('remove_prefix'):
|
||||
if data.get('remove_prefix') in self.prefixes:
|
||||
self.prefixes.remove(data.get('remove_prefix'))
|
||||
if len(self.prefixes) <= 0:
|
||||
self.prefixes = [os.environ['DISCORD_DEFAULT_PREFIX'], ]
|
||||
|
||||
self.save()
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def get_guild_by_id(cls, id):
|
||||
try:
|
||||
return cls.objects.get(id=id)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def create_guild(cls, data):
|
||||
id = data.get('id')
|
||||
if not id:
|
||||
return create_error_response('ID is required',
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if cls.get_guild_by_id(id):
|
||||
return create_error_response('That Guild already exists',
|
||||
status.HTTP_409_CONFLICT)
|
||||
|
||||
guild = cls(
|
||||
id=id,
|
||||
prefixes=data.get('prefixes'),
|
||||
new_patron_message=data.get('new_patron_message')
|
||||
)
|
||||
guild.save()
|
||||
return create_success_response(guild, status.HTTP_201_CREATED, many=False)
|
||||
|
||||
|
||||
class Role(models.Model):
|
||||
id = models.CharField(max_length=30, primary_key=True)
|
||||
guild = models.ForeignKey(Guild, on_delete=models.CASCADE, null=False)
|
||||
role_type = models.PositiveSmallIntegerField()
|
||||
|
||||
def update_role(self, data):
|
||||
if data.get('role_type'):
|
||||
self.role_type = data.get('role_type')
|
||||
|
||||
self.save()
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def add_new_role(cls, guild_id, data):
|
||||
id = data.get('id')
|
||||
role_type = data.get('role_type')
|
||||
if not (id and guild_id and (role_type is not None)):
|
||||
return create_error_response("The Role ID, Guild, and Role Type are required",
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if cls.get_role_by_id(id):
|
||||
return create_error_response("That Role Already Exists",
|
||||
status=status.HTTP_409_CONFLICT)
|
||||
guild = Guild.get_guild_by_id(guild_id)
|
||||
if not isinstance(guild, Guild):
|
||||
return create_error_response("Guild Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
try:
|
||||
role_type = int(role_type)
|
||||
except ValueError:
|
||||
return create_error_response("Role Type must be a positive number",
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
if role_type < 0:
|
||||
return create_error_response("Role Type must be a positive number",
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
elif 1000 < role_type:
|
||||
return create_error_response("Role Type must be less than 1000",
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
role = cls(
|
||||
id=id,
|
||||
guild=guild,
|
||||
role_type=role_type
|
||||
)
|
||||
role.save()
|
||||
return create_role_success_response(role, status.HTTP_201_CREATED, many=False)
|
||||
|
||||
@classmethod
|
||||
def get_role_by_id(cls, guild_id, role_id):
|
||||
try:
|
||||
return cls.get_guild_roles(guild_id).get(id=role_id)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_guild_roles(cls, guild):
|
||||
return cls.objects.filter(guild__id=guild)
|
||||
|
||||
@classmethod
|
||||
def get_admin_roles(cls, guild_id):
|
||||
try:
|
||||
return cls.get_guild_roles(guild_id).filter(role_type__gte=90)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.guild.id} | {self.id}"
|
||||
@ -1,8 +0,0 @@
|
||||
from rest_framework.permissions import BasePermission
|
||||
|
||||
class GuildPermissions(BasePermission):
|
||||
def has_permission(self, request, view):
|
||||
return super().has_permission(request, view)
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
return super().has_object_permission(request, view, obj)
|
||||
@ -1,16 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from geeksbot_v2.guilds.models import Guild
|
||||
from geeksbot_v2.guilds.models import Role
|
||||
|
||||
|
||||
class GuildSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Guild
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class RoleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Role
|
||||
fields = ["id", "guild", "role_type"]
|
||||
@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@ -1,21 +0,0 @@
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
||||
|
||||
def create_error_response(msg, status=status.HTTP_404_NOT_FOUND):
|
||||
return Response({'details': msg},
|
||||
status=status)
|
||||
|
||||
|
||||
def create_success_response(guild_data, status, many: bool = False):
|
||||
from .serializers import GuildSerializer
|
||||
|
||||
return Response(GuildSerializer(guild_data, many=many).data,
|
||||
status=status)
|
||||
|
||||
|
||||
def create_role_success_response(role_data, status, many: bool = False):
|
||||
from .serializers import RoleSerializer
|
||||
|
||||
return Response(RoleSerializer(role_data, many=many).data,
|
||||
status=status)
|
||||
@ -1,133 +0,0 @@
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import status
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
from geeksbot_v2.utils.api_utils import PaginatedAPIView
|
||||
from .models import Guild
|
||||
from .models import Role
|
||||
from .utils import create_error_response
|
||||
from .utils import create_success_response
|
||||
from .utils import create_role_success_response
|
||||
|
||||
# Create your views here.
|
||||
|
||||
# API Views
|
||||
|
||||
|
||||
class GuildsAPI(PaginatedAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, format=None):
|
||||
guilds = Guild.objects.all()
|
||||
page = self.paginate_queryset(guilds)
|
||||
if page is not None:
|
||||
return create_success_response(page, status.HTTP_200_OK, many=True)
|
||||
|
||||
return create_success_response(guilds, status.HTTP_200_OK, many=True)
|
||||
|
||||
def post(self, request, format=None):
|
||||
data = dict(request.data)
|
||||
return Guild.create_guild(data)
|
||||
|
||||
|
||||
class GuildDetail(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, id, format=None):
|
||||
try:
|
||||
guild = Guild.objects.get(id=id)
|
||||
except ObjectDoesNotExist:
|
||||
return create_error_response("Guild Does not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
else:
|
||||
return create_success_response(guild,
|
||||
status=status.HTTP_200_OK)
|
||||
|
||||
def put(self, request, id, format=None):
|
||||
guild = Guild.get_guild_by_id(id)
|
||||
|
||||
if guild:
|
||||
data = dict(request.data)
|
||||
guild = guild.update_guild(data)
|
||||
return create_success_response(guild,
|
||||
status=status.HTTP_202_ACCEPTED)
|
||||
else:
|
||||
return create_error_response('Guild Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def delete(self, request, id, format=None):
|
||||
guild = Guild.get_guild_by_id(id)
|
||||
|
||||
if guild:
|
||||
# data = dict(request.data)
|
||||
# TODO Add a check to verify user is allowed to delete...
|
||||
# Possibly in object permissions...
|
||||
guild.delete()
|
||||
return create_success_response(guild,
|
||||
status=status.HTTP_200_OK)
|
||||
else:
|
||||
return create_error_response('Guild Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
class RolesAPI(PaginatedAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, guild_id, format=None):
|
||||
roles = Role.get_guild_roles(guild_id)
|
||||
page = self.paginate_queryset(roles)
|
||||
if page is not None:
|
||||
return create_success_response(page, status.HTTP_200_OK, many=True)
|
||||
|
||||
return create_success_response(roles, status.HTTP_200_OK, many=True)
|
||||
|
||||
def post(self, request, guild_id, format=None):
|
||||
data = dict(request.data)
|
||||
return Role.add_new_role(guild_id, data)
|
||||
|
||||
|
||||
class AdminRolesAPI(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, guild_id, format=None):
|
||||
roles = Role.get_admin_roles(guild_id)
|
||||
if roles:
|
||||
return create_role_success_response(roles, status=status.HTTP_200_OK, many=True)
|
||||
return create_error_response('There are no admin roles configured',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def put(self, request, guild_id, format=None):
|
||||
data = dict(request.data)
|
||||
role = Role.get_role_by_id(guild_id, data['role'])
|
||||
if role:
|
||||
role = role.update_role({'role_type': 100})
|
||||
return create_role_success_response(role, status=status.HTTP_202_ACCEPTED)
|
||||
return create_error_response("That role does not exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
class RoleDetailAPI(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, guild_id, id, format=None):
|
||||
try:
|
||||
role = Role.objects.get(id=id)
|
||||
except ObjectDoesNotExist:
|
||||
return create_error_response("Guild Does not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
else:
|
||||
return create_role_success_response(role,
|
||||
status=status.HTTP_200_OK)
|
||||
|
||||
def put(self, request, guild_id, id, format=None):
|
||||
role = Role.get_role_by_id(guild_id, id)
|
||||
|
||||
if role:
|
||||
data = dict(request.data)
|
||||
role = role.update_role(data)
|
||||
return create_role_success_response(role,
|
||||
status=status.HTTP_202_ACCEPTED)
|
||||
else:
|
||||
return create_error_response('Guild Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "geeksbot_v2.config.settings.local")
|
||||
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError:
|
||||
# The above import may fail for some other reason. Ensure that the
|
||||
# issue is really that Django is missing to avoid masking other
|
||||
# exceptions on Python 2.
|
||||
try:
|
||||
import django # noqa
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
)
|
||||
|
||||
raise
|
||||
|
||||
# This allows easy placement of apps within the interior
|
||||
# geeksbot_v2 directory.
|
||||
current_path = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.append(os.path.join(current_path, "geeksbot_v2"))
|
||||
|
||||
execute_from_command_line(sys.argv)
|
||||
@ -1,9 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
|
||||
from .models import PatreonCreator
|
||||
from .models import PatreonTier
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(PatreonCreator)
|
||||
admin.site.register(PatreonTier)
|
||||
@ -1,7 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class PatreonConfig(AppConfig):
|
||||
name = 'geeksbot_v2.patreon'
|
||||
verbose_name = _("Patreon")
|
||||
@ -1,37 +0,0 @@
|
||||
# Generated by Django 2.2.4 on 2019-09-20 21:39
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('guilds', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PatreonCreator',
|
||||
fields=[
|
||||
('creator', models.CharField(max_length=50, primary_key=True, serialize=False)),
|
||||
('link', models.CharField(max_length=100, unique=True)),
|
||||
('guilds', models.ManyToManyField(to='guilds.Guild')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PatreonTier',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=50)),
|
||||
('description', models.TextField()),
|
||||
('amount', models.IntegerField(null=True)),
|
||||
('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='patreon.PatreonCreator')),
|
||||
('guild', models.ManyToManyField(to='guilds.Guild')),
|
||||
('next_lower_tier', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='patreon.PatreonTier')),
|
||||
('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Role')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@ -1,164 +0,0 @@
|
||||
from django.db import models
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from rest_framework import status
|
||||
|
||||
from geeksbot_v2.guilds.models import Guild
|
||||
from geeksbot_v2.guilds.models import Role
|
||||
from .utils import create_error_response
|
||||
from .utils import create_success_creator_response
|
||||
from .utils import create_success_tier_response
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
||||
class PatreonCreator(models.Model):
|
||||
guilds = models.ManyToManyField(Guild)
|
||||
creator = models.CharField(max_length=50, null=False, primary_key=True)
|
||||
link = models.CharField(max_length=100, null=False, unique=True)
|
||||
|
||||
def update_creator(self, data):
|
||||
if data.get('guild'):
|
||||
guild = Guild.get_guild_by_id(data.get('guild'))
|
||||
if not isinstance(guild, Guild):
|
||||
return create_error_response('Guild Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
self.guilds.add(guild)
|
||||
if data.get('link'):
|
||||
self.link = data.get('link')
|
||||
|
||||
self.save()
|
||||
return create_success_creator_response(self, status.HTTP_202_ACCEPTED, many=False)
|
||||
|
||||
@classmethod
|
||||
def add_new_creator(cls, data):
|
||||
creator = data.get('creator')
|
||||
if PatreonCreator.get_creator_by_name(creator):
|
||||
return create_error_response('That Creator already exists',
|
||||
status=status.HTTP_409_CONFLICT)
|
||||
link = data.get('link')
|
||||
if not (creator and link):
|
||||
return create_error_response('Creator and Link are both required fields',
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
guild = Guild.get_guild_by_id(data.get('guild'))
|
||||
if not guild:
|
||||
return create_error_response('A Valid Guild is required',
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
new_creator = cls(
|
||||
creator=creator,
|
||||
link=link
|
||||
)
|
||||
new_creator.save()
|
||||
new_creator.guilds.add(guild)
|
||||
return create_success_creator_response(new_creator, status.HTTP_201_CREATED, many=False)
|
||||
|
||||
@classmethod
|
||||
def get_creator_by_name(cls, name):
|
||||
try:
|
||||
return cls.objects.get(creator=name)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.guild.id} | {self.creator}"
|
||||
|
||||
|
||||
class PatreonTier(models.Model):
|
||||
creator = models.ForeignKey(PatreonCreator, on_delete=models.CASCADE)
|
||||
guild = models.ManyToManyField(Guild)
|
||||
name = models.CharField(max_length=50)
|
||||
description = models.TextField()
|
||||
role = models.ForeignKey(Role, on_delete=models.CASCADE)
|
||||
amount = models.IntegerField(null=True)
|
||||
next_lower_tier = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True)
|
||||
|
||||
def update_tier(self, data):
|
||||
if data.get('guild'):
|
||||
guild = Guild.get_guild_by_id(data.get('guild'))
|
||||
if not isinstance(guild, Guild):
|
||||
return create_error_response('Guild Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
self.guilds.add(guild)
|
||||
if data.get('name'):
|
||||
self.name = data.get('name')
|
||||
if data.get('description'):
|
||||
self.description = data.get('description')
|
||||
if data.get('role'):
|
||||
role = Role.get_role_by_id(data.get('role'))
|
||||
if not isinstance(role, Role):
|
||||
return create_error_response('Role Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
self.role = role
|
||||
if data.get('amount'):
|
||||
self.amount = data.get('amount')
|
||||
if data.get('next_lower_tier'):
|
||||
tier = self.get_tier_by_id(data.get('next_lower_tier'))
|
||||
if not isinstance(tier, self.__class__):
|
||||
return create_error_response('Next Lower Tier Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
self.next_lower_tier = tier
|
||||
|
||||
self.save()
|
||||
return create_success_tier_response(tier, status.HTTP_202_ACCEPTED, many=False)
|
||||
|
||||
@classmethod
|
||||
def get_tier_by_id(cls, id):
|
||||
try:
|
||||
return cls.objects.get(id=id)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def add_new_tier(cls, data):
|
||||
creator_str = data.get('creator')
|
||||
guild_id = data.get('guild')
|
||||
name = data.get('name')
|
||||
description = data.get('description')
|
||||
role_id = data.get('role')
|
||||
next_lower_tier_id = data.get('next_lower_tier')
|
||||
if not (creator_str and guild_id and name and description and role_id):
|
||||
return create_error_response("The Creator's name, Guild, Tier name, Description, "
|
||||
"and Discord Role are all required.",
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
creator = PatreonCreator.get_creator_by_name(creator_str)
|
||||
if not isinstance(creator, PatreonCreator):
|
||||
return create_error_response("Creator Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
guild = Guild.get_guild_by_id(guild_id)
|
||||
if not isinstance(guild, Guild):
|
||||
return create_error_response("Guild Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
role = Role.get_role_by_id(role_id)
|
||||
if not isinstance(role, Role):
|
||||
return create_error_response("Role Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
if next_lower_tier_id:
|
||||
next_lower_tier = cls.get_tier_by_id(next_lower_tier_id)
|
||||
if not isinstance(next_lower_tier, cls):
|
||||
return create_error_response("Next Lower Tier Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
if next_lower_tier.guild != guild:
|
||||
return create_error_response("The given next lower tier is not for the same guild.",
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
if next_lower_tier.creator != creator:
|
||||
return create_error_response("The given next lower tier is not for the same creator.",
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
try:
|
||||
PatreonTier.objects.filter(creator=creator, guilds__id=guild.id).get(name=name)
|
||||
except ObjectDoesNotExist:
|
||||
tier = cls(
|
||||
creator=creator,
|
||||
name=name,
|
||||
description=description,
|
||||
role=role,
|
||||
amount=data.get('amount'),
|
||||
next_lower_tier=next_lower_tier if next_lower_tier_id else None
|
||||
)
|
||||
tier.save()
|
||||
return create_success_tier_response(tier, status.HTTP_201_CREATED, many=False)
|
||||
else:
|
||||
return create_error_response("A Tier with that name already exists for that creator in this guild.",
|
||||
status=status.HTTP_409_CONFLICT)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.guild.id} | {self.creator.creator} | {self.name}"
|
||||
@ -1,77 +0,0 @@
|
||||
import discord
|
||||
import gspread
|
||||
from oauth2client.service_account import ServiceAccountCredentials
|
||||
|
||||
|
||||
class Patron:
|
||||
def __init__(self, *, discord_name: str=None, steam_id: int=None, patreon_tier: str=None, patron_of: str=None,
|
||||
discord_discrim: int=None, discord_id: int=None, patreon_name: str=None, steam_name: str=None):
|
||||
self.discord_name = discord_name
|
||||
self.discord_discrim = discord_discrim
|
||||
self.steam_id = steam_id
|
||||
self.discord_id = discord_id
|
||||
self.patreon_tier = patreon_tier
|
||||
self.patron_of = patron_of
|
||||
self.patreon_name = patreon_name
|
||||
self.steam_name = steam_name
|
||||
|
||||
@classmethod
|
||||
async def from_id(cls, bot, steam_id: int, *, discord_id: int=None):
|
||||
scope = ['https://spreadsheets.google.com/feeds',
|
||||
'https://www.googleapis.com/auth/drive']
|
||||
credentials = ServiceAccountCredentials.from_json_keyfile_dict(bot.google_secret, scope)
|
||||
|
||||
gc = gspread.authorize(credentials)
|
||||
sh = gc.open_by_key(bot.bot_secrets['sheet'])
|
||||
ws = sh.worksheet('Current Whitelist')
|
||||
try:
|
||||
cell = ws.find(f'{steam_id}')
|
||||
except gspread.CellNotFound:
|
||||
return -1
|
||||
else:
|
||||
steam_name = None
|
||||
if discord_id:
|
||||
user_ref = bot.fs_db.document(f'users/{discord_id}')
|
||||
user_info = (await bot.loop.run_in_executor(bot.tpe, user_ref.get)).to_dict()
|
||||
if user_info:
|
||||
steam_name = user_info.get('steam_name')
|
||||
row = ws.row_values(cell.row)
|
||||
return cls(patreon_name=row[1],
|
||||
discord_name=row[2],
|
||||
steam_id=row[5],
|
||||
patreon_tier=row[4].split(' (')[1].strip(')') if len(row[4].split(' (')) > 1 else row[4],
|
||||
patron_of=row[3].split(' (')[0],
|
||||
discord_id=discord_id,
|
||||
steam_name=steam_name)
|
||||
|
||||
@classmethod
|
||||
async def from_name(cls, bot, discord_name: discord.Member, *, discord_id: int=None):
|
||||
scope = ['https://spreadsheets.google.com/feeds',
|
||||
'https://www.googleapis.com/auth/drive']
|
||||
credentials = ServiceAccountCredentials.from_json_keyfile_dict(bot.google_secret, scope)
|
||||
|
||||
gc = gspread.authorize(credentials)
|
||||
sh = gc.open_by_key(bot.bot_secrets['sheet'])
|
||||
ws = sh.worksheet('Current Whitelist')
|
||||
try:
|
||||
cell = ws.find(f'{discord_name.name if isinstance(discord_name, discord.Member) else discord_name}')
|
||||
except gspread.CellNotFound:
|
||||
try:
|
||||
cell = ws.find(f'{discord_name.nick if isinstance(discord_name, discord.Member) else discord_name}')
|
||||
except gspread.CellNotFound:
|
||||
return -1
|
||||
steam_name = None
|
||||
discord_id = discord_name.id if isinstance(discord_name, discord.Member) else discord_id
|
||||
if discord_id:
|
||||
user_ref = bot.fs_db.document(f'users/{discord_id}')
|
||||
user_info = (await bot.loop.run_in_executor(bot.tpe, user_ref.get)).to_dict()
|
||||
if user_info:
|
||||
steam_name = user_info.get('steam_name')
|
||||
row = ws.row_values(cell.row)
|
||||
return cls(patreon_name=row[1],
|
||||
discord_name=row[2],
|
||||
discord_id=discord_id,
|
||||
steam_id=row[5],
|
||||
patreon_tier=row[4].split(' (')[1].strip(')') if len(row[4].split(' (')) > 1 else row[4],
|
||||
patron_of=row[3].split(' (')[0],
|
||||
steam_name=steam_name)
|
||||
@ -1,16 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from geeksbot_v2.patreon.models import PatreonCreator
|
||||
from geeksbot_v2.patreon.models import PatreonTier
|
||||
|
||||
|
||||
class PatreonCreatorSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = PatreonCreator
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class PatreonTierSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = PatreonTier
|
||||
fields = "__all__"
|
||||
@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@ -1,21 +0,0 @@
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
||||
|
||||
def create_error_response(msg, status=status.HTTP_404_NOT_FOUND):
|
||||
return Response({'details': msg},
|
||||
status=status)
|
||||
|
||||
|
||||
def create_success_creator_response(creator_data, status, many: bool = False):
|
||||
from .serializers import PatreonCreatorSerializer
|
||||
|
||||
return Response(PatreonCreatorSerializer(creator_data, many=many).data,
|
||||
status=status)
|
||||
|
||||
|
||||
def create_success_tier_response(tier_data, status, many: bool = False):
|
||||
from .serializers import PatreonTierSerializer
|
||||
|
||||
return Response(PatreonTierSerializer(tier_data, many=many).data,
|
||||
status=status)
|
||||
@ -1,3 +0,0 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
@ -1,6 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import RconServer
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(RconServer)
|
||||
@ -1,10 +0,0 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import RCONServersAPI, RCONServerDetailAPI, ListPlayers
|
||||
|
||||
app_name = "rcon_api"
|
||||
urlpatterns = [
|
||||
path("<str:guild_id>/", view=RCONServersAPI.as_view(), name='guild_servers'),
|
||||
path("<str:guild_id>/<str:name>/", view=RCONServerDetailAPI.as_view(), name="server_detail"),
|
||||
path("<str:guild_id>/<str:name>/listplayers", view=ListPlayers.as_view(), name='listplayers'),
|
||||
]
|
||||
@ -1,7 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class RconConfig(AppConfig):
|
||||
name = 'geeksbot_v2.rcon'
|
||||
verbose_name = _("Rcon")
|
||||
@ -1,35 +0,0 @@
|
||||
# Generated by Django 2.2.4 on 2019-09-20 21:39
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('guilds', '0001_initial'),
|
||||
('dmessages', '0001_initial'),
|
||||
('channels', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='RconServer',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=50)),
|
||||
('ip', models.GenericIPAddressField()),
|
||||
('port', models.PositiveIntegerField()),
|
||||
('password', models.CharField(max_length=50)),
|
||||
('monitor_chat', models.BooleanField()),
|
||||
('alerts_channel', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='channels.Channel')),
|
||||
('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
|
||||
('info_channel', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='channels.Channel')),
|
||||
('info_message', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='dmessages.Message')),
|
||||
('monitor_chat_channel', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='channels.Channel')),
|
||||
('settings_message', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='dmessages.Message')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@ -1,22 +0,0 @@
|
||||
# Generated by Django 2.2.4 on 2019-09-20 21:39
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('rcon', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='rconserver',
|
||||
name='whitelist',
|
||||
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||