Improve docs.
authorRaphaël Barrois <raphael.barrois@polytechnique.org>
Sun, 31 Mar 2013 01:00:25 +0000 (03:00 +0200)
committerRaphaël Barrois <raphael.barrois@polytechnique.org>
Sun, 31 Mar 2013 01:00:25 +0000 (03:00 +0200)
Makefile
doc/architecture.rst [new file with mode: 0644]
doc/coding_rules.rst [new file with mode: 0644]
doc/events.rst
doc/index.rst
doc/setup.rst
doc/tools.rst [new file with mode: 0644]

index e463792..df7847b 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -3,18 +3,64 @@ ROOT_DIR = xnet
 APPS = $(shell find $(ROOT_DIR) -name 'models.py' | sed 's,/models.py$$,,; s:.*/::')
 
 
+# In order to help newcomers, this variable holds a full doc of the current Makefile.
+# Please keep it up to date with regard to new commands.
+#
+# Structure:
+# - Group commands in sections
+# - Align command descriptions
+
+define help
+Makefile command help
+
+Available targets are:
+
+Running:
+  run:       Start a development server on http://127.0.0.1:8000/
+  shell:      Open a development Python shell using the current database
+
+Database:
+  resetdb:    Reinitialize the database schema
+
+Testing:
+  test:              Run the test suite
+
+Misc:
+  clean:      Cleanup all temporary files (*.pyc, ...)
+  doc:        Generate the documentation
+  help:       Display this help message
+endef
+
 default: all
 
 
 all:
 
 
+help:
+       @echo -n ""  # Don't display extra lines.
+       $(info $(help))
+
+
+.PHONY: all default help
+
+
+# Running
+# =======
+
 run:
        $(MANAGE_PY) runserver
 
 shell:
        $(MANAGE_PY) shell
 
+
+.PHONY: run shell
+
+# Development
+# ===========
+
+
 test:
        $(MANAGE_PY) test $(APPS)
 
@@ -22,6 +68,11 @@ resetdb:
        rm -f xnet/db.sqlite
        $(MANAGE_PY) syncdb --noinput
 
+.PHONY: resetdb test
+
+# Misc
+# ====
+
 clean:
        find . "(" -name "*.pyc" -or -name "*.pyo" -or -name "*.mo" ")" -delete
        find . -type d -empty -delete
@@ -30,4 +81,4 @@ doc:
        $(MAKE) -C doc html
 
 
-.PHONY: default all run shell test resetdb doc
+.PHONY: clean doc
diff --git a/doc/architecture.rst b/doc/architecture.rst
new file mode 100644 (file)
index 0000000..af7d77f
--- /dev/null
@@ -0,0 +1,58 @@
+Architecture
+============
+
+The plat/al project relies heavily on Django's notion of applications.
+
+
+Applications
+------------
+
+Each functional unit (events, news, groups, accounts, profiles, payments, ...)
+should live in its own Django app.
+
+Such apps should follow the following layout:
+
+- ``__init__.py``: Mandatory for Python, should be empty
+- ``models.py``: Required by Django, contains all models
+- ``admin.py``: A (properly designed) Admin for each model
+- ``factories.py``: The set of `factories <http://factoryboy.rtfd.org/>`_ related to the models
+- ``tests.py``: The set of tests for the app
+
+If the application contains views, it should include the following files:
+
+- ``forms.py``: Home of the forms used in the app (if any)
+- ``views.py``: All view definitions
+- ``urls.py``: URL map of the application, to be included in ``xnet.urls``
+- ``templates/my_app/base.html``: Base template for the application.
+  Can be simply:
+
+  .. code-block:: html
+
+      {% extends "base.html" %}
+
+- ``templates/my_app/foo.html``: app-specific templates. They should all inherit
+  from ``my_app/base.html``.
+
+
+External libraries
+------------------
+
+Whenever a functionality can be provided by an external, existing library,
+that library should be added to the dependencies (and not copied to our code).
+
+- If it's a "production" dependency, add it to ``requirements.txt``.
+- If the dependency is only useful for development / testing / ..., add it to ``dev_requirements.txt``
+- Until plat/al has reached production, avoid too specific version ranges;
+  use ``foo>=1.1.3`` if using a feature only available in v1.1.3,
+  and avoid ``foo=1.1.3`` that would prevent us from seeing new features of the lib.
+
+
+Internal libraries (utilities)
+------------------------------
+
+Utilities should be placed in the ``xnet/utils/`` folder, with one "app" per subject.
+For instance, time-related functions could be provided in ``xnet/utils/time`` while
+image-related tools would live in ``xnet/utils/image``.
+
+
+
diff --git a/doc/coding_rules.rst b/doc/coding_rules.rst
new file mode 100644 (file)
index 0000000..aeebb90
--- /dev/null
@@ -0,0 +1,100 @@
+Style guide
+===========
+
+This document describes the main coding rules in use in the plat/al codebase.
+
+Python
+------
+
+Plat/al mostly follows the :pep:8 rules and `Django's coding style`_.
+
+.. _Django's coding style: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/
+
+Mostly:
+
+  - Lines are limited to 120 characters
+  - Indent with 4 spaces
+
+Annotations
+"""""""""""
+
+- Use ``TODO`` and ``FIXME`` annotations with the name of the assignee::
+
+    # TODO(x2006barrois): make the spam more explicit
+    # FIXME(x2008gelineau): this may fry the eggs
+
+- Use ``XXX`` for things we can't improve, such as limitations in Python::
+
+    # XXX: OrderedDict does not exist before Python 2.7
+
+
+Imports
+"""""""
+
+**Do not use** ``import *``.
+
+When importing from the same package, use relative imports.
+Otherwise, use absolute imports:
+
+.. code-block:: python
+
+    # xnet/foo/bar.py
+
+    # Absolute import of xnet.blih
+    from xnet import blih
+
+    # Relative import of xnet.foo.baz
+    from . import baz
+
+
+Imports should be ordered from most general to most local:
+
+#. Standard lib imports
+#. Django imports
+#. Third party lib imports
+#. Plat/al imports
+#. Current app imports
+
+
+Exceptions
+""""""""""
+
+Exceptions are quite powerful, but should be used properly.
+
+Each module defining custom exceptions should define a base class for its
+exceptions; other exceptions from the module must then inherit from that base
+exception. This makes it much easier to catch module-specific exceptions:
+
+
+.. code-block:: python
+
+    # blah/foo.py
+
+    class FooError(Exception):
+        """Base class for exceptions from blah.foo."""
+
+    class SpecificException(FooError):
+        """A specific exception."""
+
+    def some_fun():
+        """Demo function for exception catching."""
+        try:
+            do_something()
+        except SpecificException as e:
+            handle_specific_exception(e)
+
+    # module bar.py
+
+    def other_fun():
+        try:
+            do_something()
+        except foo.FooError as e:
+            handle_generic_foo_exception(e)
+
+
+Only catch the errors that you expect. Let the other errors propagate. Do not catch an exception then raise another one, as this hides the true cause of the error.
+
+Avoid generic excepts, or worse, naked excepts:
+
+- ``except Exception:`` will catch import or syntax exceptions
+- ``except:`` and ``except BaseException:`` will catch all exceptions, including ``KeyboardInterrupt``, thus preventing ``^C``.
index 1dba197..8f2400c 100644 (file)
@@ -22,14 +22,14 @@ The Event has no price option for the global event, it is handled by the main Ev
 This is done to avoid duplicating code for main price options and part price options.
 
 
-Alternate proposed implementation
----------------------------------
+Alternate proposed implementation (1)
+-------------------------------------
 
 Same thing as above except that price options are only for the main event.
 That way the code is a bit less ugly.
 
 
-Alternate proposed implementation
----------------------------------
+Alternate proposed implementation (2)
+-------------------------------------
 
 Have EventParts have a foreign key to the main event part. The "root" of the event becomes the main part.
index 7149b2b..f682640 100644 (file)
@@ -8,15 +8,42 @@ Plat/al 2
 
 Plat/al 2 is the second version of the plat/al (platform for alumni) developed by Polytechnique.org.
 
-It relies on Django.
+It relies on `the Django framework <http://www.djangoproject.com>`_.
 
 
-Contents:
+This documentation mostly focuses on developer setup, conventions, code architecture.
+
+Getting started
+---------------
+
+If you're new to the project, the following documents should help understanding and contributing:
+
+- :doc:`setup`: How to setup your development environment
+- :doc:`coding_rules`: Coding style in use in the project
+- :doc:`architecture`: General architecture and code layout
+- :doc:`tools`: Common tools used during development
+
+
+Design docs
+-----------
+
+Those documents describe the design and thoughts about various core parts of plat/al:
+
+- :doc:`events`: Managing events, registrations, prices
+
+
+Contents
+--------
 
 .. toctree::
    :maxdepth: 2
 
    setup
+   coding_rules
+   architecture
+   tools
+
+   events
 
 
 Indices and tables
index 30c16d6..df184aa 100644 (file)
@@ -56,6 +56,10 @@ The development database is backed by sqlite, and should work out of the box:
     make resetdb
 
 
+.. note:: The provided Makefile holds a few helpful commands.
+          Use ``make help`` to get a list of available commands.
+
+
 You may now populate your database with some development data:
 
 .. code-block:: sh
diff --git a/doc/tools.rst b/doc/tools.rst
new file mode 100644 (file)
index 0000000..b0fa09a
--- /dev/null
@@ -0,0 +1,37 @@
+Development tools
+=================
+
+Plat/al relies on a few tools and base libraries.
+
+Django
+------
+
+Our base framework, we're using the latest released version (currently 1.5.1).
+Feel free to browse its excellent documentation at https://docs.djangoproject.com/
+
+
+Bootstrap
+---------
+
+Our web layout relies on Twitter's Bootstrap framework.
+We use a minified version based on Bootstrap v2.2.2.
+
+The documentation is available at http://twitter.github.com/bootstrap/.
+
+
+factory_boy
+-----------
+
+For testing, we rely on `FactoryBoy <http://factoryboy.rtfd.org>`_.
+This library allows to define custom "factories" for each model,
+which ease populating the database (in ``xnet.datasets``) or writing tests:
+
+.. code-block:: python
+
+    class SomeTestCase(unittest.TestCase):
+        def test_disabled_account(self):
+            # Get a disabled account, don't bother filling any required field.
+            account = factories.AccountFactory(is_active=False, password='foo')
+
+            self.assertFalse(
+                auth.authenticate(account.username, 'foo'))