diff --git a/.gitignore b/.gitignore index 4875ad398..ff3c03c11 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ venv staticfiles .env db.sqlite3 -getting-started/* \ No newline at end of file +database.ini +database.txt +getting-started/* diff --git a/docs/__init__.py b/docs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/admin.py b/docs/admin.py new file mode 100644 index 000000000..8c38f3f3d --- /dev/null +++ b/docs/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/docs/apps.py b/docs/apps.py new file mode 100644 index 000000000..a59399777 --- /dev/null +++ b/docs/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class DocsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'docs' diff --git a/docs/migrations/0001_initial.py b/docs/migrations/0001_initial.py new file mode 100644 index 000000000..02aebcfdd --- /dev/null +++ b/docs/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# Generated by Django 4.0 on 2021-12-28 14:24 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='Snippet', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(auto_now_add=True)), + ('title', models.CharField(blank=True, default='', max_length=100)), + ('code', models.TextField()), + ('linenos', models.BooleanField(default=False)), + ('language', models.CharField(choices=[('abap', 'ABAP'), ('abnf', 'ABNF'), ('actionscript', 'ActionScript'), ('actionscript3', 'ActionScript 3'), ('ada', 'Ada'), ('adl', 'ADL'), ('agda', 'Agda'), ('aheui', 'Aheui'), ('alloy', 'Alloy'), ('ambienttalk', 'AmbientTalk'), ('amdgpu', 'AMDGPU'), ('ampl', 'Ampl'), ('ansys', 'ANSYS parametric design language'), ('antlr', 'ANTLR'), ('antlr-actionscript', 'ANTLR With ActionScript Target'), ('antlr-cpp', 'ANTLR With CPP Target'), ('antlr-csharp', 'ANTLR With C# Target'), ('antlr-java', 'ANTLR With Java Target'), ('antlr-objc', 'ANTLR With ObjectiveC Target'), ('antlr-perl', 'ANTLR With Perl Target'), ('antlr-python', 'ANTLR With Python Target'), ('antlr-ruby', 'ANTLR With Ruby Target'), ('apacheconf', 'ApacheConf'), ('apl', 'APL'), ('applescript', 'AppleScript'), ('arduino', 'Arduino'), ('arrow', 'Arrow'), ('asc', 'ASCII armored'), ('aspectj', 'AspectJ'), ('aspx-cs', 'aspx-cs'), ('aspx-vb', 'aspx-vb'), ('asymptote', 'Asymptote'), ('augeas', 'Augeas'), ('autohotkey', 'autohotkey'), ('autoit', 'AutoIt'), ('awk', 'Awk'), ('bare', 'BARE'), ('basemake', 'Base Makefile'), ('bash', 'Bash'), ('batch', 'Batchfile'), ('bbcbasic', 'BBC Basic'), ('bbcode', 'BBCode'), ('bc', 'BC'), ('befunge', 'Befunge'), ('bibtex', 'BibTeX'), ('blitzbasic', 'BlitzBasic'), ('blitzmax', 'BlitzMax'), ('bnf', 'BNF'), ('boa', 'Boa'), ('boo', 'Boo'), ('boogie', 'Boogie'), ('brainfuck', 'Brainfuck'), ('bst', 'BST'), ('bugs', 'BUGS'), ('c', 'C'), ('c-objdump', 'c-objdump'), ('ca65', 'ca65 assembler'), ('cadl', 'cADL'), ('camkes', 'CAmkES'), ('capdl', 'CapDL'), ('capnp', "Cap'n Proto"), ('cbmbas', 'CBM BASIC V2'), ('cddl', 'CDDL'), ('ceylon', 'Ceylon'), ('cfc', 'Coldfusion CFC'), ('cfengine3', 'CFEngine3'), ('cfm', 'Coldfusion HTML'), ('cfs', 'cfstatement'), ('chaiscript', 'ChaiScript'), ('chapel', 'Chapel'), ('charmci', 'Charmci'), ('cheetah', 'Cheetah'), ('cirru', 'Cirru'), ('clay', 'Clay'), ('clean', 'Clean'), ('clojure', 'Clojure'), ('clojurescript', 'ClojureScript'), ('cmake', 'CMake'), ('cobol', 'COBOL'), ('cobolfree', 'COBOLFree'), ('coffeescript', 'CoffeeScript'), ('common-lisp', 'Common Lisp'), ('componentpascal', 'Component Pascal'), ('console', 'Bash Session'), ('coq', 'Coq'), ('cpp', 'C++'), ('cpp-objdump', 'cpp-objdump'), ('cpsa', 'CPSA'), ('cr', 'Crystal'), ('crmsh', 'Crmsh'), ('croc', 'Croc'), ('cryptol', 'Cryptol'), ('csharp', 'C#'), ('csound', 'Csound Orchestra'), ('csound-document', 'Csound Document'), ('csound-score', 'Csound Score'), ('css', 'CSS'), ('css+django', 'CSS+Django/Jinja'), ('css+genshitext', 'CSS+Genshi Text'), ('css+lasso', 'CSS+Lasso'), ('css+mako', 'CSS+Mako'), ('css+mozpreproc', 'CSS+mozpreproc'), ('css+myghty', 'CSS+Myghty'), ('css+php', 'CSS+PHP'), ('css+ruby', 'CSS+Ruby'), ('css+smarty', 'CSS+Smarty'), ('cuda', 'CUDA'), ('cypher', 'Cypher'), ('cython', 'Cython'), ('d', 'D'), ('d-objdump', 'd-objdump'), ('dart', 'Dart'), ('dasm16', 'DASM16'), ('debcontrol', 'Debian Control file'), ('debsources', 'Debian Sourcelist'), ('delphi', 'Delphi'), ('devicetree', 'Devicetree'), ('dg', 'dg'), ('diff', 'Diff'), ('django', 'Django/Jinja'), ('docker', 'Docker'), ('doscon', 'MSDOS Session'), ('dpatch', 'Darcs Patch'), ('dtd', 'DTD'), ('duel', 'Duel'), ('dylan', 'Dylan'), ('dylan-console', 'Dylan session'), ('dylan-lid', 'DylanLID'), ('earl-grey', 'Earl Grey'), ('easytrieve', 'Easytrieve'), ('ebnf', 'EBNF'), ('ec', 'eC'), ('ecl', 'ECL'), ('eiffel', 'Eiffel'), ('elixir', 'Elixir'), ('elm', 'Elm'), ('emacs-lisp', 'EmacsLisp'), ('email', 'E-mail'), ('erb', 'ERB'), ('erl', 'Erlang erl session'), ('erlang', 'Erlang'), ('evoque', 'Evoque'), ('execline', 'execline'), ('extempore', 'xtlang'), ('ezhil', 'Ezhil'), ('factor', 'Factor'), ('fan', 'Fantom'), ('fancy', 'Fancy'), ('felix', 'Felix'), ('fennel', 'Fennel'), ('fish', 'Fish'), ('flatline', 'Flatline'), ('floscript', 'FloScript'), ('forth', 'Forth'), ('fortran', 'Fortran'), ('fortranfixed', 'FortranFixed'), ('foxpro', 'FoxPro'), ('freefem', 'Freefem'), ('fsharp', 'F#'), ('fstar', 'FStar'), ('futhark', 'Futhark'), ('gap', 'GAP'), ('gas', 'GAS'), ('gcode', 'g-code'), ('gdscript', 'GDScript'), ('genshi', 'Genshi'), ('genshitext', 'Genshi Text'), ('gherkin', 'Gherkin'), ('glsl', 'GLSL'), ('gnuplot', 'Gnuplot'), ('go', 'Go'), ('golo', 'Golo'), ('gooddata-cl', 'GoodData-CL'), ('gosu', 'Gosu'), ('graphviz', 'Graphviz'), ('groff', 'Groff'), ('groovy', 'Groovy'), ('gsql', 'GSQL'), ('gst', 'Gosu Template'), ('haml', 'Haml'), ('handlebars', 'Handlebars'), ('haskell', 'Haskell'), ('haxe', 'Haxe'), ('haxeml', 'Hxml'), ('hexdump', 'Hexdump'), ('hlsl', 'HLSL'), ('hsail', 'HSAIL'), ('hspec', 'Hspec'), ('html', 'HTML'), ('html+cheetah', 'HTML+Cheetah'), ('html+django', 'HTML+Django/Jinja'), ('html+evoque', 'HTML+Evoque'), ('html+genshi', 'HTML+Genshi'), ('html+handlebars', 'HTML+Handlebars'), ('html+lasso', 'HTML+Lasso'), ('html+mako', 'HTML+Mako'), ('html+myghty', 'HTML+Myghty'), ('html+ng2', 'HTML + Angular2'), ('html+php', 'HTML+PHP'), ('html+smarty', 'HTML+Smarty'), ('html+twig', 'HTML+Twig'), ('html+velocity', 'HTML+Velocity'), ('http', 'HTTP'), ('hybris', 'Hybris'), ('hylang', 'Hy'), ('i6t', 'Inform 6 template'), ('icon', 'Icon'), ('idl', 'IDL'), ('idris', 'Idris'), ('iex', 'Elixir iex session'), ('igor', 'Igor'), ('inform6', 'Inform 6'), ('inform7', 'Inform 7'), ('ini', 'INI'), ('io', 'Io'), ('ioke', 'Ioke'), ('irc', 'IRC logs'), ('isabelle', 'Isabelle'), ('j', 'J'), ('jags', 'JAGS'), ('jasmin', 'Jasmin'), ('java', 'Java'), ('javascript', 'JavaScript'), ('javascript+cheetah', 'JavaScript+Cheetah'), ('javascript+django', 'JavaScript+Django/Jinja'), ('javascript+lasso', 'JavaScript+Lasso'), ('javascript+mako', 'JavaScript+Mako'), ('javascript+mozpreproc', 'Javascript+mozpreproc'), ('javascript+myghty', 'JavaScript+Myghty'), ('javascript+php', 'JavaScript+PHP'), ('javascript+ruby', 'JavaScript+Ruby'), ('javascript+smarty', 'JavaScript+Smarty'), ('jcl', 'JCL'), ('jlcon', 'Julia console'), ('js+genshitext', 'JavaScript+Genshi Text'), ('jsgf', 'JSGF'), ('jslt', 'JSLT'), ('json', 'JSON'), ('jsonld', 'JSON-LD'), ('jsp', 'Java Server Page'), ('julia', 'Julia'), ('juttle', 'Juttle'), ('kal', 'Kal'), ('kconfig', 'Kconfig'), ('kmsg', 'Kernel log'), ('koka', 'Koka'), ('kotlin', 'Kotlin'), ('kuin', 'Kuin'), ('lasso', 'Lasso'), ('lean', 'Lean'), ('less', 'LessCss'), ('lighttpd', 'Lighttpd configuration file'), ('limbo', 'Limbo'), ('liquid', 'liquid'), ('literate-agda', 'Literate Agda'), ('literate-cryptol', 'Literate Cryptol'), ('literate-haskell', 'Literate Haskell'), ('literate-idris', 'Literate Idris'), ('livescript', 'LiveScript'), ('llvm', 'LLVM'), ('llvm-mir', 'LLVM-MIR'), ('llvm-mir-body', 'LLVM-MIR Body'), ('logos', 'Logos'), ('logtalk', 'Logtalk'), ('lsl', 'LSL'), ('lua', 'Lua'), ('make', 'Makefile'), ('mako', 'Mako'), ('maql', 'MAQL'), ('markdown', 'Markdown'), ('mask', 'Mask'), ('mason', 'Mason'), ('mathematica', 'Mathematica'), ('matlab', 'Matlab'), ('matlabsession', 'Matlab session'), ('meson', 'Meson'), ('mime', 'MIME'), ('minid', 'MiniD'), ('miniscript', 'MiniScript'), ('modelica', 'Modelica'), ('modula2', 'Modula-2'), ('monkey', 'Monkey'), ('monte', 'Monte'), ('moocode', 'MOOCode'), ('moonscript', 'MoonScript'), ('mosel', 'Mosel'), ('mozhashpreproc', 'mozhashpreproc'), ('mozpercentpreproc', 'mozpercentpreproc'), ('mql', 'MQL'), ('mscgen', 'Mscgen'), ('mupad', 'MuPAD'), ('mxml', 'MXML'), ('myghty', 'Myghty'), ('mysql', 'MySQL'), ('nasm', 'NASM'), ('ncl', 'NCL'), ('nemerle', 'Nemerle'), ('nesc', 'nesC'), ('nestedtext', 'NestedText'), ('newlisp', 'NewLisp'), ('newspeak', 'Newspeak'), ('ng2', 'Angular2'), ('nginx', 'Nginx configuration file'), ('nimrod', 'Nimrod'), ('nit', 'Nit'), ('nixos', 'Nix'), ('nodejsrepl', 'Node.js REPL console session'), ('notmuch', 'Notmuch'), ('nsis', 'NSIS'), ('numpy', 'NumPy'), ('nusmv', 'NuSMV'), ('objdump', 'objdump'), ('objdump-nasm', 'objdump-nasm'), ('objective-c', 'Objective-C'), ('objective-c++', 'Objective-C++'), ('objective-j', 'Objective-J'), ('ocaml', 'OCaml'), ('octave', 'Octave'), ('odin', 'ODIN'), ('omg-idl', 'OMG Interface Definition Language'), ('ooc', 'Ooc'), ('opa', 'Opa'), ('openedge', 'OpenEdge ABL'), ('output', 'Text output'), ('pacmanconf', 'PacmanConf'), ('pan', 'Pan'), ('parasail', 'ParaSail'), ('pawn', 'Pawn'), ('peg', 'PEG'), ('perl', 'Perl'), ('perl6', 'Perl6'), ('php', 'PHP'), ('pig', 'Pig'), ('pike', 'Pike'), ('pkgconfig', 'PkgConfig'), ('plpgsql', 'PL/pgSQL'), ('pointless', 'Pointless'), ('pony', 'Pony'), ('postgresql', 'PostgreSQL SQL dialect'), ('postscript', 'PostScript'), ('pot', 'Gettext Catalog'), ('pov', 'POVRay'), ('powershell', 'PowerShell'), ('praat', 'Praat'), ('procfile', 'Procfile'), ('prolog', 'Prolog'), ('promql', 'PromQL'), ('properties', 'Properties'), ('protobuf', 'Protocol Buffer'), ('psql', 'PostgreSQL console (psql)'), ('psysh', 'PsySH console session for PHP'), ('pug', 'Pug'), ('puppet', 'Puppet'), ('pwsh-session', 'PowerShell Session'), ('py2tb', 'Python 2.x Traceback'), ('pycon', 'Python console session'), ('pypylog', 'PyPy Log'), ('pytb', 'Python Traceback'), ('python', 'Python'), ('python2', 'Python 2.x'), ('qbasic', 'QBasic'), ('qml', 'QML'), ('qvto', 'QVTO'), ('racket', 'Racket'), ('ragel', 'Ragel'), ('ragel-c', 'Ragel in C Host'), ('ragel-cpp', 'Ragel in CPP Host'), ('ragel-d', 'Ragel in D Host'), ('ragel-em', 'Embedded Ragel'), ('ragel-java', 'Ragel in Java Host'), ('ragel-objc', 'Ragel in Objective C Host'), ('ragel-ruby', 'Ragel in Ruby Host'), ('rbcon', 'Ruby irb session'), ('rconsole', 'RConsole'), ('rd', 'Rd'), ('reasonml', 'ReasonML'), ('rebol', 'REBOL'), ('red', 'Red'), ('redcode', 'Redcode'), ('registry', 'reg'), ('resourcebundle', 'ResourceBundle'), ('restructuredtext', 'reStructuredText'), ('rexx', 'Rexx'), ('rhtml', 'RHTML'), ('ride', 'Ride'), ('rng-compact', 'Relax-NG Compact'), ('roboconf-graph', 'Roboconf Graph'), ('roboconf-instances', 'Roboconf Instances'), ('robotframework', 'RobotFramework'), ('rql', 'RQL'), ('rsl', 'RSL'), ('ruby', 'Ruby'), ('rust', 'Rust'), ('sarl', 'SARL'), ('sas', 'SAS'), ('sass', 'Sass'), ('scala', 'Scala'), ('scaml', 'Scaml'), ('scdoc', 'scdoc'), ('scheme', 'Scheme'), ('scilab', 'Scilab'), ('scss', 'SCSS'), ('sgf', 'SmartGameFormat'), ('shen', 'Shen'), ('shexc', 'ShExC'), ('sieve', 'Sieve'), ('silver', 'Silver'), ('singularity', 'Singularity'), ('slash', 'Slash'), ('slim', 'Slim'), ('slurm', 'Slurm'), ('smali', 'Smali'), ('smalltalk', 'Smalltalk'), ('smarty', 'Smarty'), ('smithy', 'Smithy'), ('sml', 'Standard ML'), ('snobol', 'Snobol'), ('snowball', 'Snowball'), ('solidity', 'Solidity'), ('sp', 'SourcePawn'), ('sparql', 'SPARQL'), ('spec', 'RPMSpec'), ('splus', 'S'), ('sql', 'SQL'), ('sqlite3', 'sqlite3con'), ('squidconf', 'SquidConf'), ('ssp', 'Scalate Server Page'), ('stan', 'Stan'), ('stata', 'Stata'), ('supercollider', 'SuperCollider'), ('swift', 'Swift'), ('swig', 'SWIG'), ('systemverilog', 'systemverilog'), ('tads3', 'TADS 3'), ('tap', 'TAP'), ('tasm', 'TASM'), ('tcl', 'Tcl'), ('tcsh', 'Tcsh'), ('tcshcon', 'Tcsh Session'), ('tea', 'Tea'), ('teal', 'teal'), ('teratermmacro', 'Tera Term macro'), ('termcap', 'Termcap'), ('terminfo', 'Terminfo'), ('terraform', 'Terraform'), ('tex', 'TeX'), ('text', 'Text only'), ('thrift', 'Thrift'), ('ti', 'ThingsDB'), ('tid', 'tiddler'), ('tnt', 'Typographic Number Theory'), ('todotxt', 'Todotxt'), ('toml', 'TOML'), ('trac-wiki', 'MoinMoin/Trac Wiki markup'), ('trafficscript', 'TrafficScript'), ('treetop', 'Treetop'), ('tsql', 'Transact-SQL'), ('turtle', 'Turtle'), ('twig', 'Twig'), ('typescript', 'TypeScript'), ('typoscript', 'TypoScript'), ('typoscriptcssdata', 'TypoScriptCssData'), ('typoscripthtmldata', 'TypoScriptHtmlData'), ('ucode', 'ucode'), ('unicon', 'Unicon'), ('urbiscript', 'UrbiScript'), ('usd', 'USD'), ('vala', 'Vala'), ('vb.net', 'VB.net'), ('vbscript', 'VBScript'), ('vcl', 'VCL'), ('vclsnippets', 'VCLSnippets'), ('vctreestatus', 'VCTreeStatus'), ('velocity', 'Velocity'), ('verilog', 'verilog'), ('vgl', 'VGL'), ('vhdl', 'vhdl'), ('vim', 'VimL'), ('wast', 'WebAssembly'), ('wdiff', 'WDiff'), ('webidl', 'Web IDL'), ('whiley', 'Whiley'), ('x10', 'X10'), ('xml', 'XML'), ('xml+cheetah', 'XML+Cheetah'), ('xml+django', 'XML+Django/Jinja'), ('xml+evoque', 'XML+Evoque'), ('xml+lasso', 'XML+Lasso'), ('xml+mako', 'XML+Mako'), ('xml+myghty', 'XML+Myghty'), ('xml+php', 'XML+PHP'), ('xml+ruby', 'XML+Ruby'), ('xml+smarty', 'XML+Smarty'), ('xml+velocity', 'XML+Velocity'), ('xorg.conf', 'Xorg'), ('xquery', 'XQuery'), ('xslt', 'XSLT'), ('xtend', 'Xtend'), ('xul+mozpreproc', 'XUL+mozpreproc'), ('yaml', 'YAML'), ('yaml+jinja', 'YAML+Jinja'), ('yang', 'YANG'), ('zeek', 'Zeek'), ('zephir', 'Zephir'), ('zig', 'Zig')], default='python', max_length=100)), + ('style', models.CharField(choices=[('abap', 'abap'), ('algol', 'algol'), ('algol_nu', 'algol_nu'), ('arduino', 'arduino'), ('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful'), ('default', 'default'), ('emacs', 'emacs'), ('friendly', 'friendly'), ('fruity', 'fruity'), ('gruvbox-dark', 'gruvbox-dark'), ('gruvbox-light', 'gruvbox-light'), ('igor', 'igor'), ('inkpot', 'inkpot'), ('lovelace', 'lovelace'), ('manni', 'manni'), ('material', 'material'), ('monokai', 'monokai'), ('murphy', 'murphy'), ('native', 'native'), ('paraiso-dark', 'paraiso-dark'), ('paraiso-light', 'paraiso-light'), ('pastie', 'pastie'), ('perldoc', 'perldoc'), ('rainbow_dash', 'rainbow_dash'), ('rrt', 'rrt'), ('sas', 'sas'), ('solarized-dark', 'solarized-dark'), ('solarized-light', 'solarized-light'), ('stata', 'stata'), ('stata-dark', 'stata-dark'), ('stata-light', 'stata-light'), ('tango', 'tango'), ('trac', 'trac'), ('vim', 'vim'), ('vs', 'vs'), ('xcode', 'xcode'), ('zenburn', 'zenburn')], default='friendly', max_length=100)), + ('highlighted', models.TextField()), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='snippets', to='auth.user')), + ], + options={ + 'ordering': ['created'], + }, + ), + ] diff --git a/docs/migrations/0002_doc_doctopic_folder_topic_delete_snippet_and_more.py b/docs/migrations/0002_doc_doctopic_folder_topic_delete_snippet_and_more.py new file mode 100644 index 000000000..542fc36e5 --- /dev/null +++ b/docs/migrations/0002_doc_doctopic_folder_topic_delete_snippet_and_more.py @@ -0,0 +1,80 @@ +# Generated by Django 4.0 on 2022-01-05 20:33 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ('docs', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Doc', + fields=[ + ('id', models.BigAutoField(auto_created=True, + primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('document', models.BinaryField()), + ('folder', models.TextField()), + ('created', models.DateTimeField(auto_now_add=True)), + ('owner', models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, related_name='docs', to='auth.user')), + ], + options={ + 'ordering': ['created'], + }, + ), + migrations.CreateModel( + name='DocTopic', + fields=[ + ('id', models.BigAutoField(auto_created=True, + primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(auto_now_add=True)), + ('doc_id', models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to='docs.doc')), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, + related_name='doctopics', to='auth.user')), + ], + ), + migrations.CreateModel( + name='Folder', + fields=[ + ('id', models.BigAutoField(auto_created=True, + primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('created', models.DateTimeField(auto_now_add=True)), + ('owner', models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, related_name='folders', to='auth.user')), + ('parent', models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to='docs.folder')), + ], + ), + migrations.CreateModel( + name='Topic', + fields=[ + ('id', models.BigAutoField(auto_created=True, + primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=30)), + ('short_desc', models.CharField( + blank=True, default='', max_length=100)), + ('description', models.CharField( + blank=True, default='', max_length=1000)), + ('created', models.DateTimeField(auto_now_add=True)), + ('owner', models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, related_name='topics', to='auth.user')), + ], + ), + migrations.DeleteModel( + name='Snippet', + ), + migrations.AddField( + model_name='doctopic', + name='topic_id', + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to='docs.topic'), + ), + ] diff --git a/docs/migrations/0003_alter_doc_options_alter_doc_folder_alter_doc_owner_and_more.py b/docs/migrations/0003_alter_doc_options_alter_doc_folder_alter_doc_owner_and_more.py new file mode 100644 index 000000000..7b9480b1f --- /dev/null +++ b/docs/migrations/0003_alter_doc_options_alter_doc_folder_alter_doc_owner_and_more.py @@ -0,0 +1,39 @@ +# Generated by Django 4.0 on 2022-01-06 17:10 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ('docs', '0002_doc_doctopic_folder_topic_delete_snippet_and_more'), + ] + + operations = [ + migrations.AlterModelOptions( + name='doc', + options={}, + ), + migrations.AlterField( + model_name='doc', + name='folder', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='docs.folder'), + ), + migrations.AlterField( + model_name='doc', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='docs', to='auth.user'), + ), + migrations.AlterField( + model_name='folder', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='folders', to='auth.user'), + ), + migrations.AlterField( + model_name='folder', + name='parent', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='docs.folder'), + ), + ] diff --git a/docs/migrations/0004_alter_folder_parent.py b/docs/migrations/0004_alter_folder_parent.py new file mode 100644 index 000000000..65d0ec6da --- /dev/null +++ b/docs/migrations/0004_alter_folder_parent.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0 on 2022-01-06 20:23 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('docs', '0003_alter_doc_options_alter_doc_folder_alter_doc_owner_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='folder', + name='parent', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.RESTRICT, to='docs.folder'), + ), + ] diff --git a/docs/migrations/0005_alter_folder_owner.py b/docs/migrations/0005_alter_folder_owner.py new file mode 100644 index 000000000..4a5ce1c41 --- /dev/null +++ b/docs/migrations/0005_alter_folder_owner.py @@ -0,0 +1,20 @@ +# Generated by Django 4.0 on 2022-01-06 20:25 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ('docs', '0004_alter_folder_parent'), + ] + + operations = [ + migrations.AlterField( + model_name='folder', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='fldrs', to='auth.user'), + ), + ] diff --git a/docs/migrations/0006_remove_doctopic_owner_remove_topic_owner_and_more.py b/docs/migrations/0006_remove_doctopic_owner_remove_topic_owner_and_more.py new file mode 100644 index 000000000..dc772a80b --- /dev/null +++ b/docs/migrations/0006_remove_doctopic_owner_remove_topic_owner_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 4.0 on 2022-01-07 15:03 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ('docs', '0005_alter_folder_owner'), + ] + + operations = [ + migrations.RemoveField( + model_name='doctopic', + name='owner', + ), + migrations.RemoveField( + model_name='topic', + name='owner', + ), + migrations.AlterField( + model_name='doc', + name='folder', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='docs', to='docs.folder'), + ), + migrations.AlterField( + model_name='folder', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='folders', to='auth.user'), + ), + migrations.AlterField( + model_name='folder', + name='parent', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.RESTRICT, related_name='folders', to='docs.folder'), + ), + ] diff --git a/docs/migrations/0007_alter_doctopic_unique_together.py b/docs/migrations/0007_alter_doctopic_unique_together.py new file mode 100644 index 000000000..3b8bfbf71 --- /dev/null +++ b/docs/migrations/0007_alter_doctopic_unique_together.py @@ -0,0 +1,17 @@ +# Generated by Django 4.0 on 2022-01-07 15:54 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('docs', '0006_remove_doctopic_owner_remove_topic_owner_and_more'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='doctopic', + unique_together={('doc_id', 'topic_id')}, + ), + ] diff --git a/docs/migrations/0008_alter_doctopic_unique_together.py b/docs/migrations/0008_alter_doctopic_unique_together.py new file mode 100644 index 000000000..d12bf0526 --- /dev/null +++ b/docs/migrations/0008_alter_doctopic_unique_together.py @@ -0,0 +1,17 @@ +# Generated by Django 4.0 on 2022-01-07 15:55 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('docs', '0007_alter_doctopic_unique_together'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='doctopic', + unique_together=set(), + ), + ] diff --git a/docs/migrations/0009_alter_doctopic_unique_together.py b/docs/migrations/0009_alter_doctopic_unique_together.py new file mode 100644 index 000000000..0dbbe0ae6 --- /dev/null +++ b/docs/migrations/0009_alter_doctopic_unique_together.py @@ -0,0 +1,17 @@ +# Generated by Django 4.0 on 2022-01-07 15:57 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('docs', '0008_alter_doctopic_unique_together'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='doctopic', + unique_together={('doc_id', 'topic_id')}, + ), + ] diff --git a/docs/migrations/0010_alter_doctopic_options.py b/docs/migrations/0010_alter_doctopic_options.py new file mode 100644 index 000000000..3f89484ca --- /dev/null +++ b/docs/migrations/0010_alter_doctopic_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.0 on 2022-01-07 16:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('docs', '0009_alter_doctopic_unique_together'), + ] + + operations = [ + migrations.AlterModelOptions( + name='doctopic', + options={'ordering': ['doc_id']}, + ), + ] diff --git a/docs/migrations/0011_doc_topics.py b/docs/migrations/0011_doc_topics.py new file mode 100644 index 000000000..e4b640b13 --- /dev/null +++ b/docs/migrations/0011_doc_topics.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2022-01-07 19:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('docs', '0010_alter_doctopic_options'), + ] + + operations = [ + migrations.AddField( + model_name='doc', + name='topics', + field=models.ManyToManyField(through='docs.DocTopic', to='docs.Topic'), + ), + ] diff --git a/docs/migrations/0012_topic_docs.py b/docs/migrations/0012_topic_docs.py new file mode 100644 index 000000000..90506163c --- /dev/null +++ b/docs/migrations/0012_topic_docs.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2022-01-07 19:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('docs', '0011_doc_topics'), + ] + + operations = [ + migrations.AddField( + model_name='topic', + name='docs', + field=models.ManyToManyField(through='docs.DocTopic', to='docs.Doc'), + ), + ] diff --git a/docs/migrations/0013_rename_doc_document_alter_document_options_and_more.py b/docs/migrations/0013_rename_doc_document_alter_document_options_and_more.py new file mode 100644 index 000000000..ed337504a --- /dev/null +++ b/docs/migrations/0013_rename_doc_document_alter_document_options_and_more.py @@ -0,0 +1,30 @@ +# Generated by Django 4.0 on 2022-01-07 20:36 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ('docs', '0012_topic_docs'), + ] + + operations = [ + migrations.RenameModel( + old_name='Doc', + new_name='Document', + ), + migrations.AlterModelOptions( + name='document', + options={'ordering': ['name']}, + ), + migrations.AlterModelOptions( + name='folder', + options={'ordering': ['name']}, + ), + migrations.AlterModelOptions( + name='topic', + options={'ordering': ['name']}, + ), + ] diff --git a/docs/migrations/__init__.py b/docs/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/models.py b/docs/models.py new file mode 100644 index 000000000..b81ad652c --- /dev/null +++ b/docs/models.py @@ -0,0 +1,68 @@ +from django.db import models + + +class Folder(models.Model): + """ + A folder is alanogous to a file system folder. It can have a parent + folder or multiple child folders. + """ + name = models.CharField(max_length=100) + parent = models.ForeignKey( + 'self', related_name='folders', null=True, on_delete=models.RESTRICT) + created = models.DateTimeField(auto_now_add=True) + owner = models.ForeignKey('auth.User', related_name='folders', + on_delete=models.RESTRICT) + + class Meta: + ordering = ['name'] + + +class Topic(models.Model): + """ + A topic is a category used for research or grouping of documents. + """ + name = models.CharField(max_length=30) + short_desc = models.CharField(max_length=100, blank=True, default='') + description = models.CharField(max_length=1000, blank=True, default='') + docs = models.ManyToManyField('Document', through='DocTopic') + created = models.DateTimeField(auto_now_add=True) + # owner = models.ForeignKey('auth.User', related_name='topics', + # on_delete=models.CASCADE) + + class Meta: + ordering = ['name'] + + +class Document(models.Model): + """ + A document is a file stored in the system. + + This model assumes it will be stored in the database. If at a lter stage + the files are stored in a file system, the document field would be removed + and the file would then be stored and retrieved from the actual folder + of the file system. + """ + name = models.CharField(max_length=100) + document = models.BinaryField() + folder = models.ForeignKey(Folder, related_name='docs', + on_delete=models.RESTRICT) + topics = models.ManyToManyField(Topic, through='DocTopic') + created = models.DateTimeField(auto_now_add=True) + owner = models.ForeignKey('auth.User', related_name='docs', + on_delete=models.RESTRICT) + + class Meta: + ordering = ['name'] + + +class DocTopic(models.Model): + doc_id = models.ForeignKey(Document, on_delete=models.CASCADE) + topic_id = models.ForeignKey(Topic, on_delete=models.CASCADE) + created = models.DateTimeField(auto_now_add=True) + # owner = models.ForeignKey('auth.User', related_name='doctopics', + # on_delete=models.CASCADE) + + class Meta: + unique_together = ('doc_id', 'topic_id') + ordering = ['doc_id'] + # order_by = 'doc_id' diff --git a/docs/permissions.py b/docs/permissions.py new file mode 100644 index 000000000..7510b2806 --- /dev/null +++ b/docs/permissions.py @@ -0,0 +1,16 @@ +from rest_framework import permissions + + +class IsOwnerOrReadOnly(permissions.BasePermission): + """ + Custom permission to only allow owners of an object to edit it. + """ + + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request, + # so we'll always allow GET, HEAD or OPTIONS requests. + if request.method in permissions.SAFE_METHODS: + return True + + # Write permissions are only allowed to the owner of the snippet. + return obj.owner == request.user diff --git a/docs/serializers.py b/docs/serializers.py new file mode 100644 index 000000000..719d560ab --- /dev/null +++ b/docs/serializers.py @@ -0,0 +1,59 @@ +from rest_framework import serializers +# from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES +from docs.models import Document, Folder, Topic, DocTopic +from django.contrib.auth.models import User + + +class FolderSerializer(serializers.HyperlinkedModelSerializer): + parent_name = serializers.ReadOnlyField(source='parent.name') + docs = serializers.HyperlinkedRelatedField( + many=True, view_name='document-detail', read_only=True) + owner = serializers.ReadOnlyField(source='owner.username') + + class Meta: + model = Folder + fields = ['id', 'url', 'name', + 'parent_name', 'parent', + 'docs', + 'owner'] + + +class DocSerializer(serializers.HyperlinkedModelSerializer): + folder_name = serializers.ReadOnlyField(source='folder.name') + owner = serializers.ReadOnlyField(source='owner.username') + + class Meta: + model = Document + fields = ['id', 'url', 'name', 'document', + 'folder_name', 'folder', + 'topics', + 'owner'] + + +class TopicSerializer(serializers.HyperlinkedModelSerializer): + + class Meta: + model = Topic + fields = ['id', 'url', 'name', 'short_desc', 'description', 'docs'] + + +class DocTopicSerializer(serializers.HyperlinkedModelSerializer): + docs = DocSerializer(read_only=True, many=True) + topics = TopicSerializer(read_only=True, many=True) + # doc_name = serializers.ReadOnlyField(source='document.name') + + class Meta: + model = DocTopic + # fields = "__all__" + fields = ['id', 'url', 'created', + 'doc_id', 'topic_id', + 'docs', 'topics'] + + +class UserSerializer(serializers.HyperlinkedModelSerializer): + docs = serializers.HyperlinkedRelatedField( + many=True, view_name='doc-detail', read_only=True) + + class Meta: + model = User + fields = ['url', 'id', 'username', 'docs'] diff --git a/docs/tests.py b/docs/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/docs/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/docs/urls.py b/docs/urls.py new file mode 100644 index 000000000..d13b48d87 --- /dev/null +++ b/docs/urls.py @@ -0,0 +1,93 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from rest_framework.urlpatterns import format_suffix_patterns +from docs import views + +from docs.views import DocViewSet, FolderViewSet, TopicViewSet, DocTopicViewSet +from docs.views import UserViewSet +from docs.views import api_root + + +# Create a router and register our viewsets with it. +router = DefaultRouter() +router.register(r'docs', views.DocViewSet) +router.register(r'topics', views.TopicViewSet) +router.register(r'doctopics', views.DocTopicViewSet) +router.register(r'folders', views.FolderViewSet) + +doc_list = DocViewSet.as_view({ + 'get': 'list', + 'post': 'create' +}) +doc_detail = DocViewSet.as_view({ + 'get': 'retrieve', + 'put': 'update', + 'patch': 'partial_update', + 'delete': 'destroy' +}) + +doctopic_list = DocTopicViewSet.as_view({ + 'get': 'list', + 'post': 'create' +}) +doctopic_detail = DocTopicViewSet.as_view({ + 'get': 'retrieve', + 'put': 'update', + 'patch': 'partial_update', + 'delete': 'destroy' +}) + +folder_list = FolderViewSet.as_view({ + 'get': 'list', + 'post': 'create' +}) +folder_detail = FolderViewSet.as_view({ + 'get': 'retrieve', + 'put': 'update', + 'patch': 'partial_update', + 'delete': 'destroy' +}) + +topic_list = TopicViewSet.as_view({ + 'get': 'list', + 'post': 'create' +}) +topic_detail = TopicViewSet.as_view({ + 'get': 'retrieve', + 'put': 'update', + 'patch': 'partial_update', + 'delete': 'destroy' +}) + +user_list = UserViewSet.as_view({ + 'get': 'list' +}) +user_detail = UserViewSet.as_view({ + 'get': 'retrieve' +}) + + +# API endpoints + +""" +There needs to be a search endpoint for each of documents, folders and topics. +""" +urlpatterns = format_suffix_patterns([ + path('', api_root), + path('docs/', doc_list, name='document-list'), + path('docs//', doc_detail, name='document-detail'), + path('folders/', folder_list, name='folder-list'), + path('folders//', folder_detail, name='folder-detail'), + path('topics/', topic_list, name='topic-list'), + path('topics//', topic_detail, name='topic-detail'), + path('doctopics/', doctopic_list, name='doctopic-list'), + path('doctopics//', doctopic_detail, name='doctopic-detail'), + path('users/', user_list, name='user-list'), + path('users//', user_detail, name='user-detail'), +]) + +urlpaterns = format_suffix_patterns(urlpatterns) + +urlpatterns += [ + path('api-auth/', include('rest_framework.urls')), +] diff --git a/docs/views.py b/docs/views.py new file mode 100644 index 000000000..d39280dea --- /dev/null +++ b/docs/views.py @@ -0,0 +1,88 @@ + +# from snippets.models import Snippet +# from snippets.permissions import IsOwnerOrReadOnly +# from snippets.serializers import SnippetSerializer +# from snippets.serializers import UserSerializer + +from docs.models import Document, Folder, Topic, DocTopic +from docs.permissions import IsOwnerOrReadOnly +from docs.serializers import DocSerializer +from docs.serializers import FolderSerializer +from docs.serializers import TopicSerializer +from docs.serializers import DocTopicSerializer +from docs.serializers import UserSerializer + +# from rest_framework import generics +from rest_framework import permissions +# from rest_framework import renderers +from rest_framework import viewsets + +from rest_framework.decorators import api_view +# from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework.reverse import reverse + +from django.contrib.auth.models import User + + +@api_view +def api_root(request, format=None): + return Response({ + 'docs': reverse('document-list', request=request, format=format), + 'folders': reverse('folder-list', request=request, format=format), + 'topics': reverse('topic-list', request=request, format=format), + # 'users': reverse('user-list', request=request, format=format), + }) + + +class UserViewSet(viewsets.ReadOnlyModelViewSet): + """ + This viewset automatically provides 'list' and 'retrieve' actions. + """ + queryset = User.objects.all() + serializer_class = UserSerializer + + +class DocViewSet(viewsets.ModelViewSet): + """ + A document is in a folder and may have many topics. + """ + queryset = Document.objects.all() + serializer_class = DocSerializer + permission_classes = [permissions.IsAuthenticatedOrReadOnly, + IsOwnerOrReadOnly] + + def perform_create(self, serializer): + serializer.save(owner=self.request.user) + + +class FolderViewSet(viewsets.ModelViewSet): + """ + A folder may hold documents or folders. + + A folder should only be removed if it is empty. + """ + queryset = Folder.objects.all() + serializer_class = FolderSerializer + permission_classes = [permissions.IsAuthenticatedOrReadOnly, + IsOwnerOrReadOnly] + + def perform_create(self, serializer): + serializer.save(owner=self.request.user) + + +class TopicViewSet(viewsets.ModelViewSet): + """ + A topic is used to categorize documents. + One document may have several topics. + """ + queryset = Topic.objects.all() + serializer_class = TopicSerializer + + +class DocTopicViewSet(viewsets.ModelViewSet): + """ + The many to many data structure that connects documents to topics. + """ + queryset = DocTopic.objects.all() + serializer_class = DocTopicSerializer diff --git a/gettingstarted/settings.py b/gettingstarted/settings.py index 9ea552a0c..40105c62b 100644 --- a/gettingstarted/settings.py +++ b/gettingstarted/settings.py @@ -39,7 +39,9 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", + 'rest_framework', "hello", + 'docs.apps.DocsConfig', ] MIDDLEWARE = [ @@ -78,7 +80,7 @@ DATABASES = { "default": { - "ENGINE" : "django.db.backends.sqlite3", + "ENGINE": "django.db.backends.sqlite3", "NAME": os.path.join(BASE_DIR, "db.sqlite3") } } @@ -116,4 +118,12 @@ STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') STATIC_URL = "/static/" +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +REST_FRAMEWORK = { + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'PAGE_SIZE': 10 +} + + django_heroku.settings(locals()) diff --git a/gettingstarted/urls.py b/gettingstarted/urls.py index 13b759582..6ee172ea2 100644 --- a/gettingstarted/urls.py +++ b/gettingstarted/urls.py @@ -2,9 +2,11 @@ from django.contrib import admin +import hello.views +import docs.views + admin.autodiscover() -import hello.views # To add a new path, first import the app: # import blog @@ -16,6 +18,7 @@ urlpatterns = [ path("", hello.views.index, name="index"), + path("", include('docs.urls')), path("db/", hello.views.db, name="db"), path("admin/", admin.site.urls), ] diff --git a/hello/views.py b/hello/views.py index c2480724b..12dc835e2 100644 --- a/hello/views.py +++ b/hello/views.py @@ -1,12 +1,21 @@ + from django.shortcuts import render from django.http import HttpResponse from .models import Greeting +import requests +import os + # Create your views here. def index(request): # return HttpResponse('Hello from Python!') - return render(request, "index.html") + # return render(request, "index.html") + # r = requests.get('http://httpbin.org/status/418') + # print(r.text) + # return HttpResponse('
' + r.text + '
') + times = int(os.environ.get('TIMES', 3)) + return HttpResponse('Hello! ' * times) def db(request): diff --git a/requirements.txt b/requirements.txt index 4af6a3765..1bdf3fff6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,6 @@ django gunicorn -django-heroku \ No newline at end of file +django-heroku +requests +psycopg2 +djangorestframework