Rewrite documentation for modern Sphinx (#5)

This commit is contained in:
Iwo Herka 2018-07-23 18:22:46 +02:00 committed by Iwo Herka
parent b755c778d6
commit d9dba5703c
28 changed files with 998 additions and 618 deletions

View file

@ -8,8 +8,8 @@
## Django EAV 2 - Entity-Attribute-Value storage for Django
Django EAV 2 is a fork of django-eav (which itself was derived from eav-django).
You can find documentation <a href="https://django-eav-2.readthedocs.io/en/improvement-docs/">here</a>.
Django EAV 2 is a fork of django-eav (which itself was derived from eav-django).
You can find documentation <a href="https://django-eav-2.rtfd.io">here</a>.
## Installation
You can install **django-eav2** from three sources:

View file

@ -1,89 +1,20 @@
# Makefile for Sphinx documentation
# Minimal 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) .
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
SPHINXPROJ = DjangoEAV2
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make 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 " 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 " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@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)"
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
clean:
-rm -rf $(BUILDDIR)/*
.PHONY: help Makefile
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."
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/django-eav.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-eav.qhc"
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
"run these through (pdf)latex."
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."
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View file

View file

View file

@ -1,199 +0,0 @@
# -*- coding: utf-8 -*-
#
# django-eav documentation build configuration file, created by
# sphinx-quickstart on Fri Sep 24 10:48:33 2010.
#
# 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 sys, os
# 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.append(os.path.abspath(os.path.join('..','..')))
# django setup
import settings
from django.core.management import setup_environ
setup_environ(settings)
# -- General configuration -----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo','sphinxtogithub']
# 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'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'django-eav'
copyright = u'2010, MVP Africa'
# 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.9'
# The full version, including alpha/beta/rc tags.
release = '0.9.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 documents that shouldn't be included in the build.
#unused_docs = []
# List of directories, relative to source directory, that shouldn't be searched
# for source files.
exclude_trees = ['_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. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
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_use_modindex = 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, 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 = ''
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = ''
# Output file base name for HTML help builder.
htmlhelp_basename = 'django-eavdoc'
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'django-eav.tex', u'django-eav Documentation',
u'MVP Africa', '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
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_use_modindex = True

View file

@ -1,24 +0,0 @@
Docstrings
==========
.. automodule:: eav
:members:
.. automodule:: eav.models
:members:
.. automodule:: eav.validators
:members:
.. automodule:: eav.fields
:members:
.. automodule:: eav.forms
:members:
.. automodule:: eav.managers
:members:
.. automodule:: eav.registry
:members:

View file

@ -1,240 +0,0 @@
.. django-eav documentation master file, created by
sphinx-quickstart on Fri Sep 24 10:48:33 2010.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
##########
django-eav
##########
Introduction
============
django-eav provides an Entity-Attribute-Value storage model for django apps.
For a decent explanation of what an Entity-Attribute-Value storage model is,
check `Wikipedia
<http://en.wikipedia.org/wiki/Entity-attribute-value_model>`_.
.. note::
This software was inspired / derived from the excellent `eav-django
<http://pypi.python.org/pypi/eav-django/1.0.2>`_ written by Andrey
Mikhaylenko.
There are a few notable differences between this implementation and the
eav-django implementation.
* This one is called django-eav, whereas the other is called eav-django.
* This app allows you to to 'attach' EAV attributes to any existing django
model (even from third-party apps) without making any changes to the those
models' code.
* This app has slightly more robust (but still not perfect) filtering.
Installation
============
You can install django-eav directly from guthub::
pip install -e git+git://github.com/mvpdev/django-eav.git#egg=django-eav
After installing, add ``eav`` to your ``INSTALLED_APPS`` in your
project's ``settings.py`` file.
Usage
=====
In order to attach EAV attributes to a model, you first need to register it
(just like you may register your models with django.contrib.admin).
Registration
------------
Registering a model with eav does a few things:
* Adds the eav :class:`eav.managers.EntityManager` to your class. By default,
it will replace the default ``objects`` manager of the model, but you can
choose to have the eav manager named something else if you don't want it to
replace ``objects`` (see :ref:`advancedregistration`).
* Connects the model's ``post_init`` signal to
:meth:`~eav.registry.Registry.attach_eav_attr`. This function will attach
the eav :class:`eav.models.Entity` helper class to every instance of your
model when it is instatiated. By default, it will be attached to your models
as an attribute named ``eav``, which will allow you to access it through
``my_model_instance.eav``, but you can choose to name it something else if you
want (again see :ref:`advancedregistration`).
* Connect's the model's ``pre_save`` signal to
:meth:`eav.models.Entity.pre_save_handler`.
* Connect's the model's ``post_save`` signal to
:meth:`eav.models.Entity.post_save_handler`.
* Adds a generic relation helper to the class.
* Sets an attribute called ``_eav_config_cls`` on the model class to either
the default :class:`eav.registry.EavConfig` config class, or to the config
class you provided during registration.
If that all sounds too complicated, don't worry, you really don't need to think
about it. Just thought you should know.
Simple Registration
^^^^^^^^^^^^^^^^^^^
To register any model with EAV, you simply need to add the registration line
somewhere that will be executed when django starts::
import eav
eav.register(MyModel)
Generally, the most appropriate place for this would be in your ``models.py``
immediately after your model definition.
.. _advancedregistration:
Advanced Registration
^^^^^^^^^^^^^^^^^^^^^
Advanced registration is only required if:
* You don't want eav to replace your model's default ``objects`` manager.
* You want to name the :class:`~eav.models.Entity` helper attribute something
other than ``eav``
* You don't want all eav :class:`~eav.models.Attribute` objects to
be able to be set for all of your registered models. In other words, you
have different types of entities, each with different attributes.
Advanced registration is simple, and is performed the exact same way
you override the django.contrib.admin registration defaults.
You just need to define your own config class that subclasses
:class:`~eav.registry.EavConfig` and override the default class attributes
and method.
There are five :class:`~eav.registry.EavConfig` class attributes you can
override:
================================= ================================== ==========================================================================
Class Attribute Default Description
================================= ================================== ==========================================================================
``manager_attr`` ``'objects'`` The name of the eav manager
``manager_only`` ``False`` *boolean* Whether to *only* replace the manager, and do nothing else
``eav_attr`` ``'eav'`` The attribute name of the entity helper
``generic_relation_attr`` ``'eav_values'`` The attribute name of the generic relation helper
``generic_relation_related_name`` The model's ``__class__.__name__`` The related name of the related name of the generic relation to your model
================================= ================================== ==========================================================================
An example of just choosing a different name for the manager (and thus leaving
``objects`` intact)::
class MyEavConfigClass(EavConfig):
manager_attr = 'eav_objects'
eav.register(MyModel, MyEavConfigClass)
Additionally, :class:`~eav.registry.EavConfig` defines a classmethod called
``get_attributes`` that, by default will return ``Attribute.objects.all()``
This method is used to determine which :class:`~eav.models.Attribute` can be
applied to the entity model you are registering. If you want to limit which
attributes can be applied to your entity, you would need to override it.
For example::
class MyEavConfigClass(EavConfig):
@classmethod
def get_attributes(cls):
return Attribute.objects.filter(type='person')
eav.register(MyModel, MyEavConfigClass)
Using Attributes
================
Once you've registered your model(s), you can begin to use them with EAV
attributes. Let's assume your model is called ``Person`` and it has one
normal django ``CharField`` called name, but you want to be able to dynamically
store other data about each Person.
First, let's create some attributes::
>>> Attribute.objects.create(name='Weight', datatype=Attribute.TYPE_FLOAT)
>>> Attribute.objects.create(name='Height', datatype=Attribute.TYPE_INT)
>>> Attribute.objects.create(name='Is pregant?', datatype=Attribute.TYPE_BOOLEAN)
Now let's create a patient, and set some of these attributes::
>>> p = Patient.objects.create(name='Bob')
>>> p.eav.height = 46
>>> p.eav.weight = 42.2
>>> p.eav.is_pregnant = False
>>> p.save()
>>> bob = Patient.objects.get(name='Bob')
>>> bob.eav.height
46
>>> bob.eav.weight
42.2
>>> bob.is_pregnant
False
Additionally, assuming we're using the eav manager, we can also do::
>>> p = Patient.objects.create(name='Jen', eav__height=32, eav__pregnant=True)
Filtering
=========
eav attributes are filterable, using the same __ notation as django foreign
keys::
Patient.objects.filter(eav__weight=42.2)
Patient.objects.filter(eav__weight__gt=42)
Patient.objects.filter(name='Bob', eav__weight__gt=42)
Patient.objects.exclude(eav__is_pregnant=False)
You can even use Q objects, however there are some known issues
(see :ref:`qobjectissue`) with Q object filters::
Patient.objects.filter(Q(name='Bob') | Q(eav__is_pregnant=False))
What about if you have a foreign key to a model that uses eav, but you want
to filter from a model that doesn't use eav? For example, let's say you have
a ``Patient`` model that **doesn't** use eav, but it has a foreign key to
``Encounter`` that **does** use eav. You can even filter through eav across
this relationship, but you need to use the eav manager for ``Patient``.
Just register ``Patient`` with eav, but set ``manager_only = True``
see (see :ref:`advancedregistration`). Then you can do::
Patient.objects.filter(encounter__eav__weight=2)
Admin Integration
=================
You can even have your eav attributes show up just like normal fields in your
models admin pages. Just register using the eav admin class::
from django.contrib import admin
from eav.forms import BaseDynamicEntityForm
from eav.admin import BaseEntityAdmin
class PatientAdminForm(BaseDynamicEntityForm):
model = Patient
class PatientAdmin(BaseEntityAdmin):
form = PatientAdminForm
admin.site.register(Patient, PatientAdmin)
Known Issues
============
.. _qobjectissue:
Q Object Filters
----------------
Due to an unexplained Q object / generic relation issue, exclude filters with
EAV Q objects, or EAV Q objects ANDed together may produce inaccurate results.
Additional Resources
====================
.. toctree::
:maxdepth: 2
docstrings

View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Binary file not shown.

View file

@ -0,0 +1,66 @@
@import url('https://fonts.googleapis.com/css?family=Roboto');
@font-face {
font-family: "Roboto Slab";
src: url("./RobotoSlab-Regular.ttf");
}
pre {
background-color: #f6f6f6 !important;
}
.doc-title h1 {
text-align: center;
padding: 2rem !important;
}
.doc-api {
font-size: 14px;
}
div.sphinxsidebar h3 {
font-size: 21px !important;
}
body {
font-size: 16px;
}
div.admonition p.admonition-title, div.sphinxsidebar h3, div.sphinxsidebar h4,
div.sphinxsidebar input, div.body h1, div.body h2, div.body h3, div.body h4,
div.body h5, div.body h6 { font-family: 'Roboto Slab', 'Helvetica', 'Arial',
sans-serif; font-weight: 400; }
div.body h1, div.body h2, div.body h3, div.body h4,
div.body h5, div.body h6 { color: #353535; }
pre, code { font-family: 'Ubuntu Mono', 'Consolas', 'Menlo',
'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono';
font-size: 15px; background: transparent; }
pre, * pre { padding: 7px 0 7px 30px!important;
margin: 15px 0!important;
line-height: 1.3; }
div.body { color: #3E4349; }
a { color: #5D2CD1; }
a:hover { color: #7546E3; }
p.version-warning { background-color: #7546E3; }
a.reference { border-bottom: 1px dotted #5D2CD1; }
a.reference:hover { border-bottom: 1px solid #7546E3; }
a.footnote-reference { border-bottom: 1px dotted #5D2CD1; }
a.footnote-reference:hover { border-bottom: 1px solid #7546E3; }
a:hover code { background-color: #eeeeee; }
code.xref, a code { background-color: #E8EFF0;
border-bottom: 1px solid white; }
div.indexwrapper h1 {
text-indent: -999999px;
background: url(click.png) no-repeat center center;
height: 200px;
}
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
div.indexwrapper h1 {
background: url(click@2x.png) no-repeat center center;
background-size: 420px 175px;
}
}

View file

@ -0,0 +1,4 @@
{% extends "!layout.html" %}
{% block extrahead %}
<a href="https://github.com/makimo/django-eav2"><img style="position: fixed; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub"></a>
{% endblock %}

View file

@ -0,0 +1,11 @@
<h3>About</h3>
<p>
Django EAV 2 is an entity-attribute-value storage for modern Django.
</p>
<h3>Useful Links</h3>
<ul>
<li><a href="index.html">Home</a></li>
<li><a href="https://pypi.org/project/django-eav2/">PyPI</a></li>
<li><a href="http://github.com/makimo/django-eav2">GitHub</a></li>
<li><a href="http://github.com/makimo/django-eav2/issues">Issue Tracker</a></li>
</ul>

View file

@ -0,0 +1,3 @@
<p class="logo"><a href="{{ pathto(master_doc) }}">
<img class="logo" src="{{ pathto('_static/logo.png', 1) }}" width="250" height="50" alt="Logo">
</a></p>

74
docs/source/api.rst Normal file
View file

@ -0,0 +1,74 @@
.. rst-class:: doc-api
API Reference
=============
If you are looking for information on a specific function, class, or method,
this part of the documentation is for you.
.. toctree::
Admin
-----
.. automodule:: eav.admin
:members:
:member-order: bysource
Decorators
----------
.. automodule:: eav.decorators
:members:
:member-order: bysource
Fields
------
.. automodule:: eav.fields
:members:
:member-order: bysource
Forms
-----
.. automodule:: eav.forms
:members:
:member-order: bysource
:exclude-members: FIELD_CLASSES
Managers
--------
.. automodule:: eav.managers
:members:
:member-order: bysource
Models
------
.. automodule:: eav.models
:members:
:member-order: bysource
Queryset
--------
.. automodule:: eav.queryset
:members:
:member-order: bysource
Registry
--------
.. automodule:: eav.registry
:members:
:member-order: bysource
Validators
----------
.. automodule:: eav.validators
:members:
:member-order: bysource

192
docs/source/conf.py Normal file
View file

@ -0,0 +1,192 @@
# -*- coding: utf-8 -*-
# -- Path setup --------------------------------------------------------------
# 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.
import os
import sys
import django
from django.conf import settings
from sphinx.ext.autodoc import between
sys.path.insert(0, os.path.abspath('.'))
sys.path.insert(0, os.path.abspath('../../'))
# Pass settings into configure.
settings.configure(
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'eav'
]
)
# Call django.setup to load installed apps and other stuff.
django.setup()
# -- Project information -----------------------------------------------------
project = 'Django EAV 2'
copyright = '2018, Iwo Herka and team at MAKIMO'
author = '-'
# The short X.Y version
version = ''
# The full version, including alpha/beta/rc tags
release = '0.10.0'
# -- General configuration ---------------------------------------------------
extensions = [
'sphinx.ext.napoleon',
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.coverage',
'sphinx.ext.viewcode',
]
html_theme_options = dict(
show_powered_by = False,
show_related = True,
fixed_sidebar = True,
font_family = 'Roboto'
)
templates_path = ['_templates']
source_suffix = '.rst'
master_doc = 'index'
language = None
exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# -- 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 = 'alabaster'
# 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 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']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
def setup(app):
app.add_stylesheet('styles.css')
app.connect('autodoc-process-docstring', between('^.*IGNORE.*$', exclude=True))
return app
html_sidebars = {
'index': [
'sidebarintro.html',
'localtoc.html'
],
'**': [
'sidebarintro.html',
'localtoc.html',
'relations.html',
'searchbox.html'
]
}
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'DjangoEAV2doc'
# -- 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': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'DjangoEAV2.tex', 'Django EAV 2 Documentation',
'-', 'manual'),
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'djangoeav2', 'Django EAV 2 Documentation',
[author], 1)
]
# -- 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 = [
(master_doc, 'DjangoEAV2', 'Django EAV 2 Documentation',
author, 'DjangoEAV2', 'One line description of project.',
'Miscellaneous'),
]
# -- Extension configuration -------------------------------------------------
# -- Options for intersphinx extension ---------------------------------------
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/': None}
# -- Autodoc configuration ---------------------------------------------------
add_module_names = False

View file

@ -0,0 +1,45 @@
Getting Started
===============
Installation
------------
You can install **django-eav2** from PyPI, git or directly from source:
From PyPI
^^^^^^^^^
::
pip install django-eav2
With pip via git
^^^^^^^^^^^^^^^^
::
pip install git+https://github.com/makimo/django-eav2@master
From source
^^^^^^^^^^^
::
git clone git@github.com:makimo/django-eav2.git
cd django-eav2
python setup.py install
To uninstall::
python setup.py install --record files.txt
rm $(cat files.txt)
Configuration
-------------
After you've installed the package, you have to add it to your Django apps::
INSTALLED_APPS = [
# ...
'eav',
# ...
]

72
docs/source/index.rst Normal file
View file

@ -0,0 +1,72 @@
.. .. image:: _static/logo.png
.. :align: center
.. rst-class:: doc-title
Django EAV 2
============
Django EAV 2 is an entity-attribute-value storage for modern Django.
Getting started is very easy.
**Step 1.** Register a model:
.. code-block:: python
import eav
eav.register(Supplier)
or with decorators:
.. code-block:: python
from eav.decorators import register_eav
@register_eav
class Supplier(models.Model):
...
**Step 2.** Create an attribute:
.. code-block:: python
Attribute.objects.create(name='City', datatype=Attribute.TYPE_TEXT)
**Step 3.** That's it! You're ready to go:
.. code-block:: python
supplier.eav.city = 'London'
supplier.save()
Supplier.objects.filter(eav__city='London')
# = <EavQuerySet [<Supplier: Supplier object (1)>]>
Documentation
-------------
.. toctree::
:maxdepth: 2
getting_started
usage
API Reference
-------------
If you are looking for information on a specific function, class, or
method, this part of the documentation is for you.
.. toctree::
:maxdepth: 2
api
Indices and tables
------------------
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

243
docs/source/usage.rst Normal file
View file

@ -0,0 +1,243 @@
Usage
=====
This part of the documentation will take you through all of library's
usage patterns. Before you can use EAV attributes, however, you need to
register your models.
Simple Registration
-------------------
Basic registration is very simple. You can do it with :func:`~eav.register` method:
.. code-block:: python
import eav
eav.register(Parts)
or with decorators:
.. code-block:: python
from eav.decorators import register_eav
@register_eav
class Supplier(models.Model):
...
Generally, if you chose the former, the most appriopriate place for the
statement would be at the bottom of your ``models.py`` or immmediately after
model definition.
Advanced Registration
---------------------
Under the hood, registration does a couple of things:
1. Attaches :class:`~eav.managers.EntityManager` to your class. By default,
it replaces standard manager (*objects*). You can configure under which
attribute it is accessible with :class:`~.eav.registry.EavConfig` (see below).
2. Binds your model's *post_init* signal with
:meth:`~eav.registry.Registry.attach_eav_attr` method. It is used to
attach :class:`~eav.models.Entity` helper object to each model instance.
Entity, in turn, is used to retrieve, store and validate attribute values.
By default, it's accessible under *eav* attribute:
.. code-block:: python
part.eav.weight = 0.56
part.save()
3. Binds your model's *pre_save* and *post_save* signals to
:meth:`~eav.models.Entity.pre_save_handler` and
:meth:`~eav.models.Entity.post_save_hander`, respectively.
Those methods are responsible for validation and storage
of attribute values.
4. Setups up generic relation to :class:`~eav.models.Value` set.
By default, it's accessed under *eav_values*:
.. code-block:: python
patient.eav_values.all()
# = <QuerySet [has fever?: "True" (1), temperature: 37.7 (2)]>
5. Sets *_eav_config_cls* attribute storing model of the config class
used by :class:`~eav.registry.Registry`. Defaults
to :class:`~eav.registry.EavConfig`; can be overridden (see below).
With that out of the way, almost every aspect of the registration can
be customized. All available options are provided to registration
via config class: :class:`~eav.registry.EavConfig` passed to
:meth:`~eav.register`. You can change them by overriding the class and passing
it as a second argument. Available options are as follows:
1. ``manager_attr`` - Specifies manager name. Used to refer to the
manager from Entity class, "objects" by default.
2. ``manager_only`` - Specifies whether signals and generic relation should
be setup for the registered model.
3. ``eav_attr`` - Named of the Entity toolkit instance on the registered
model instance. "eav" by default. See attach_eav_attr.
4. ``generic_relation_attr`` - Name of the GenericRelation to Value
objects. "eav_values" by default.
5. ``generic_relation_related_name`` - Name of the related name for
GenericRelation from Entity to Value. None by default. Therefore,
if not overridden, it is not possible to query Values by Entities.
Example registration may look like:
.. code-block:: python
class SupplierEavConfig(EavConfig):
manager_attr = 'eav_objects'
eav.register(supplier, SupplierEavConfig)
.. note::
As of now, configurable registration is not supported via
class decorator. You have to use explicit method call.
Additionally, :class:`~eav.registry.EavConfig` provides *classmethod*
:meth:`~eav.registry.EavConfig.get_attributes` which is used to determine
a set of attributes available to a given model. By default, it returns
``Attribute.objects.all()``. As usual, it can be customized:
.. code-block:: python
from eav.models import Attribute
class SomeModelEavConfig(EavConfig):
@classmethod
def get_attributes(cls):
return Attribute.objects.filter(slug__startswith='a')
Attribute validation includes checks against illegal attribute value
assignments. This means that value assignments for attributes which are
excluded for the model are treated with
:class:`~eav.exceptions.IllegalAssignmentException`. For example (extending
previous one):
.. code-block:: python
some_model.eav.beard = True
some_model.save()
will throw an exception.
Creating Attributes
-------------------
Once your models are registered, you can starting creating attributes for
them. Two most important attributes of ``Attribute`` class are *slug* and
*datatype*. *slug* is a unique global identifier (there must be at most
one ``Attribute`` instance with given `slug`) and must be a valid Python
variable name, as it's used to access values for that attribute from
:class:`~eav.models.Entity` helper:
.. code-block:: python
from eav.models import Attribute
Attribute.objects.create(slug='color', datatype=Attribute.TYPE_TEXT)
flower.eav.color = 'red'
# Alternatively, assuming you're using default EntityManager:
Attribute.objects.create(slug='color', datatype=Attribute.TYPE_TEXT)
Flower.objects.create(name='rose', eav__color='red')
*datatype* determines type of attribute (and by extension type of value
stored in :class:`~eav.models.Value`). Available choices are:
========= ==================
Type Attribute Constant
========= ==================
*int* ``TYPE_INT``
*float* ``TYPE_FLOAT``
*text* ``TYPE_TEXT``
*date* ``TYPE_DATE``
*bool* ``TYPE_BOOLEAN``
*object* ``TYPE_OBJECT``
*enum* ``TYPE_ENUM``
========= ==================
If you want to create an attribute with data-type *enum*, you need to provide
it with ``enum_group``:
.. code-block:: python
from eav.models import EnumValue, EnumGroup, Attribute
true = EnumValue.objects.create(value='Yes')
false = EnumValue.objects.create(value='No')
bool_group = EnumGroup.objects.create(name='Yes / No')
bool_group.enums.add(true, false)
Attribute.objects.create(
name='hungry?',
datatype=Attribute.TYPE_ENUM,
enum_group=bool_group
)
# = <Attribute: hungry? (Multiple Choice)>
Finally, attribute type *object* allows to relate Django model instances
via generic foreign keys:
.. code-block:: python
Attribute.objects.create(name='Supplier', datatype=Attribute.TYPE_OBJECT)
steve = Supplier.objects.create(name='Steve')
cog = Part.objects.create(name='Cog', eav__supplier=steve)
cog.eav.supplier
# = <Supplier: Steve (1)>
Filtering By Attributes
-----------------------
Once you've created your attributes and values for them, you can use them
to filter Django models. Django EAV 2 is using the same notation as Django's
foreign-keys:
.. code-block:: python
Part.objects.filter(eav__weight=10)
Part.objects.filter(eav__weight__gt=10)
Part.objects.filter(eav__code__startswith='A')
# Of course, you can mix them with regular queries:
Part.objects.filter(name='Cog', eav__height=7.8)
# Querying enums looks as follows:
yes = EnumValue.objects.get(name='Yes')
Part.objects.filter(eav__is_available=yes)
You can use ``Q`` expressions too:
.. code-block:: python
Patient.objects.filter(
Q(eav__sex='male', eav__fever=no) | Q(eav__city='Nice') & Q(eav__age__gt=32)
)
Admin Integration
-----------------
Django EAV 2 includes integration for Django's admin. As usual, you need to
register your model first:
.. code-block:: python
from django.contrib import admin
from eav.forms import BaseDynamicEntityForm
from eav.admin import BaseEntityAdmin
class PatientAdminForm(BaseDynamicEntityForm):
model = Patient
class PatientAdmin(BaseEntityAdmin):
form = PatientAdminForm
admin.site.register(Patient, PatientAdmin)

View file

@ -1,8 +1,7 @@
'''Admin. This module contains classes used for admin integration.'''
'''This module contains classes used for admin integration.'''
from django.contrib import admin
from django.contrib.admin.options import InlineModelAdmin
from django.contrib.admin.options import ModelAdmin
from django.contrib.admin.options import InlineModelAdmin, ModelAdmin
from django.forms.models import BaseInlineFormSet
from django.utils.safestring import mark_safe
@ -34,18 +33,19 @@ class BaseEntityAdmin(ModelAdmin):
class BaseEntityInlineFormSet(BaseInlineFormSet):
"""
'''
An inline formset that correctly initializes EAV forms.
"""
'''
def add_fields(self, form, index):
if self.instance:
setattr(form.instance, self.fk.name, self.instance)
form._build_dynamic_fields()
super(BaseEntityInlineFormSet, self).add_fields(form, index)
class BaseEntityInline(InlineModelAdmin):
"""
'''
Inline model admin that works correctly with EAV attributes. You should mix
in the standard StackedInline or TabularInline classes in order to define
formset representation, e.g.::
@ -54,12 +54,11 @@ class BaseEntityInline(InlineModelAdmin):
model = Item
form = forms.ItemForm
.. warning: TabularInline does *not* work out of the box. There is,
.. warning:: TabularInline does *not* work out of the box. There is,
however, a patched template `admin/edit_inline/tabular.html` bundled
with EAV-Django. You can copy or symlink the `admin` directory to your
templates search path (see Django documentation).
"""
'''
formset = BaseEntityInlineFormSet
def get_fieldsets(self, request, obj=None):

View file

@ -1,14 +1,12 @@
'''
Decorators.
This module contains pure wrapper functions used as decorators.
Functions in this module should be simple and not involve complex logic.
'''
def register_eav(**kwargs):
'''
Registers the given model(s) classes and wrapped Model class with
django-eav::
Registers the given model(s) classes and wrapped ``Model`` class with
Django EAV 2::
@register_eav
class Author(models.Model):

View file

@ -1,11 +1,3 @@
'''
Fields.
Contains two custom fields:
* :class:`EavSlugField`
* :class:`EavDatatypeField`
'''
import re
from django.core.exceptions import ValidationError
@ -23,7 +15,7 @@ class EavSlugField(models.SlugField):
Slugs are used to convert the Python attribute name to a database
lookup and vice versa. We need it to be a valid Python identifier.
We don't want it to start with a '_', underscore will be used
var variables we don't want to be saved in db.
var variables we don't want to be saved in the database.
'''
super(EavSlugField, self).validate(value, instance)
slug_regex = r'[a-z][a-z0-9_]*'

View file

@ -1,4 +1,4 @@
'''Forms. This module contains forms used for admin integration.'''
'''This module contains forms used for admin integration.'''
from copy import deepcopy
@ -10,13 +10,25 @@ from django.utils.translation import ugettext_lazy as _
class BaseDynamicEntityForm(ModelForm):
'''
ModelForm for entity with support for EAV attributes. Form fields are
created on the fly depending on Schema defined for given entity instance.
``ModelForm`` for entity with support for EAV attributes. Form fields are
created on the fly depending on schema defined for given entity instance.
If no schema is defined (i.e. the entity instance has not been saved yet),
only static fields are used. However, on form validation the schema will be
retrieved and EAV fields dynamically added to the form, so when the
validation is actually done, all EAV fields are present in it (unless
Rubric is not defined).
Mapping between attribute types and field classes is as follows:
===== =============
Type Field
===== =============
text CharField
float IntegerField
int DateTimeField
bool BooleanField
enum ChoiceField
===== =============
'''
FIELD_CLASSES = {
'text': CharField,

View file

@ -1,6 +1,6 @@
'''
Managers.
# -*- coding: utf-8 -*-
'''
This module contains the custom manager used by entities registered with eav.
'''
from django.db import models

View file

@ -1,6 +1,4 @@
'''
Models.
This module defines the four concrete, non-abstract models:
* :class:`Value`
* :class:`Attribute`

View file

@ -1,26 +1,24 @@
# -*- coding: utf-8 -*-
'''
Queryset.
This module contains custom EavQuerySet class used for overriding
This module contains custom :class:`EavQuerySet` class used for overriding
relational operators and pure functions for rewriting Q-expressions.
Q-expressions need to be rewritten for two reasons:
1. In order to hide implementation from the user and provide easy to use
syntax sugar, i.e.::
1. In order to hide implementation from the user and provide easy to use
syntax sugar, i.e.::
Supplier.objects.filter(eav__city__startswith='New')
Supplier.objects.filter(eav__city__startswith='New')
instead of::
instead of::
city_values = Value.objects.filter(value__text__startswith='New')
Supplier.objects.filter(eav_values__in=city_values)
city_values = Value.objects.filter(value__text__startswith='New')
Supplier.objects.filter(eav_values__in=city_values)
For details see: ``eav_filter``.
For details see: :func:`eav_filter`.
2. To ensure that Q-expression tree is compiled to valid SQL.
For details see: ``rewrite_q_expr``.
2. To ensure that Q-expression tree is compiled to valid SQL.
For details see: :func:`rewrite_q_expr`.
'''
from functools import wraps
@ -37,7 +35,7 @@ def is_eav_and_leaf(expr, gr_name):
Checks whether Q-expression is an EAV AND leaf.
Args:
expr (Q | tuple): Q-expression to be checked.
expr (Union[Q, tuple]): Q-expression to be checked.
gr_name (str): Generic relation attribute name, by default 'eav_values'
Returns:
@ -53,6 +51,7 @@ def rewrite_q_expr(model_cls, expr):
Rewrites Q-expression to safe form, in order to ensure that
generated SQL is valid.
IGNORE:
Suppose we have the following Q-expression:
OR
@ -63,6 +62,7 @@ def rewrite_q_expr(model_cls, expr):
eav_values__in [4, 5]
AND
eav_values__in [6, 7, 8]
IGNORE
All EAV values are stored in a single table. Therefore, INNER JOIN
generated for the AND-expression (1) will always fail, i.e.
@ -71,23 +71,25 @@ def rewrite_q_expr(model_cls, expr):
two different sets). Therefore, we must paritially rewrite the
expression so that the generated SQL is valid::
IGNORE:
OR
AND
eav_values__in [1, 2, 3]
AND
pk__in [1, 2]
IGNORE
This is done by merging dangerous AND's and substituting them with
explicit ``pk__in`` filter, where pks are taken from evaluted
Q-expr branch.
Args:
model_cls (Model class): model class used to construct QuerySet
from leaf attribute-value expression.
expr: (Q | tuple): Q-expression (or attr-val leaf) to be rewritten.
model_cls (TypeVar): model class used to construct :meth:`QuerySet`
from leaf attribute-value expression.
expr: (Q | tuple): Q-expression (or attr-val leaf) to be rewritten.
Returns:
Q | tuple
Union[Q, tuple]
'''
# Node in a Q-expr can be a Q or an attribute-value tuple (leaf).
# We are only interested in Qs.
@ -153,7 +155,7 @@ def rewrite_q_expr(model_cls, expr):
def eav_filter(func):
'''
Decorator used to wrap filter and exclude methods. Passes args through
expand_q_filters and kwargs through expand_eav_filter. Returns the
:func:`expand_q_filters` and kwargs through :func:`expand_eav_filter`. Returns the
called function (filter or exclude).
'''
@wraps(func)
@ -188,7 +190,7 @@ def expand_q_filters(q, root_cls):
'''
Takes a Q object and a model class.
Recursively passes each filter / value in the Q object tree leaf nodes
through expand_eav_filter
through :func:`expand_eav_filter`.
'''
new_children = []
@ -259,23 +261,23 @@ class EavQuerySet(QuerySet):
@eav_filter
def filter(self, *args, **kwargs):
'''
Pass *args* and *kwargs* through ``eav_filter``, then pass to
the ``models.Manager`` filter method.
Pass *args* and *kwargs* through :func:`eav_filter`, then pass to
the ``Manager`` filter method.
'''
return super(EavQuerySet, self).filter(*args, **kwargs)
@eav_filter
def exclude(self, *args, **kwargs):
'''
Pass *args* and *kwargs* through ``eav_filter``, then pass to
the ``models.Manager`` exclude method.
Pass *args* and *kwargs* through :func:`eav_filter`, then pass to
the ``Manager`` exclude method.
'''
return super(EavQuerySet, self).exclude(*args, **kwargs)
@eav_filter
def get(self, *args, **kwargs):
'''
Pass *args* and *kwargs* through ``eav_filter``, then pass to
the ``models.Manager`` get method.
Pass *args* and *kwargs* through :func:`eav_filter`, then pass to
the ``Manager`` get method.
'''
return super(EavQuerySet, self).get(*args, **kwargs)

View file

@ -1,4 +1,4 @@
'''Registry. This modules contains the registry classes.'''
'''This modules contains the registry classes.'''
from django.contrib.contenttypes import fields as generic
from django.db.models.signals import post_init, post_save, pre_save
@ -9,21 +9,22 @@ from .models import Attribute, Entity, Value
class EavConfig(object):
'''
The default EavConfig class used if it is not overriden on registration.
The default ``EavConfig`` class used if it is not overriden on registration.
This is where all the default eav attribute names are defined.
Available options are as follows:
1. manager_attr - Specifies manager name. Used to refer to the
manager from Entity class, "objects" by default.
2. manager_only - Specifies whether signals and generic relation should
be setup for the registered model.
3. eav_attr - Named of the Entity toolkit instance on the registered
model instance. "eav" by default. See attach_eav_attr.
4. generic_relation_attr - Name of the GenericRelation to Value
objects. "eav_values" by default.
5. generic_relation_related_name - Name of the related name for
GenericRelation from Entity to Value. None by default. Therefore,
if not overridden, it is not possible to query Values by Entities.
1. manager_attr - Specifies manager name. Used to refer to the
manager from Entity class, "objects" by default.
2. manager_only - Specifies whether signals and generic relation should
be setup for the registered model.
3. eav_attr - Named of the Entity toolkit instance on the registered
model instance. "eav" by default. See attach_eav_attr.
4. generic_relation_attr - Name of the GenericRelation to Value
objects. "eav_values" by default.
5. generic_relation_related_name - Name of the related name for
GenericRelation from Entity to Value. None by default. Therefore,
if not overridden, it is not possible to query Values by Entities.
'''
manager_attr = 'objects'
manager_only = False

View file

@ -1,4 +1,4 @@
'''Utilities. This module contains non-essential helper methods.'''
'''This module contains non-essential helper methods.'''
import sys

View file

@ -1,13 +1,11 @@
# -*- coding: utf-8 -*-
'''
Validtors.
This module contains a validator for each Attribute datatype.
This module contains a validator for each :class:`~eav.models.Attribute` datatype.
A validator is a callable that takes a value and raises a ``ValidationError``
if it doesnt meet some criteria. (see
`django validators <http://docs.djangoproject.com/en/dev/ref/validators/>`_)
if it doesnt meet some criteria (see `Django validators
<http://docs.djangoproject.com/en/dev/ref/validators/>`_).
These validators are called by the
:meth:`~eav.models.Attribute.validate_value` method in the