├── .gitignore ├── CONTRIBUTORS.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── docs └── images │ ├── select_multiple_cropped.jpg │ └── select_multiple_cropped_src.png ├── select_multiple_field ├── __init__.py ├── codecs.py ├── forms.py ├── models.py ├── validators.py └── widgets.py ├── setup.py ├── test_projects ├── __init__.py ├── django14 │ ├── __init__.py │ ├── django14 │ │ ├── __init__.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ ├── forthewing │ │ ├── __init__.py │ │ ├── models.py │ │ ├── templates │ │ │ └── forthewing │ │ │ │ ├── base.html │ │ │ │ ├── chickenwings_confirm_delete.html │ │ │ │ ├── chickenwings_created.html │ │ │ │ ├── chickenwings_deleted.html │ │ │ │ ├── chickenwings_detail.html │ │ │ │ ├── chickenwings_form.html │ │ │ │ ├── chickenwings_list.html │ │ │ │ └── chickenwings_updated.html │ │ ├── templatetags │ │ │ ├── __init__.py │ │ │ └── chickenwings_tags.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py │ ├── global_assets │ │ ├── static │ │ │ ├── css │ │ │ │ ├── app.css │ │ │ │ └── style-multiselect.css │ │ │ ├── js │ │ │ │ └── script-multiselect.js │ │ │ └── multiselect-0.9.10 │ │ │ │ ├── .gitignore │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── README.markdown │ │ │ │ ├── bower.json │ │ │ │ ├── css │ │ │ │ └── multi-select.css │ │ │ │ ├── img │ │ │ │ └── switch.png │ │ │ │ ├── js │ │ │ │ └── jquery.multi-select.js │ │ │ │ └── multi-select.jquery.json │ │ └── templates │ │ │ ├── 404.html │ │ │ ├── 500.html │ │ │ ├── app │ │ │ ├── base.html │ │ │ └── home.html │ │ │ └── theme │ │ │ ├── html5.html │ │ │ └── site_base.html │ ├── manage.py │ ├── pizzagigi │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── models.py │ │ ├── templates │ │ │ └── pizzagigi │ │ │ │ ├── base.html │ │ │ │ ├── pizza_confirm_delete.html │ │ │ │ ├── pizza_created.html │ │ │ │ ├── pizza_deleted.html │ │ │ │ ├── pizza_detail.html │ │ │ │ ├── pizza_form.html │ │ │ │ ├── pizza_list.html │ │ │ │ └── pizza_updated.html │ │ ├── templatetags │ │ │ ├── __init__.py │ │ │ └── pizza_tags.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py │ └── suthern │ │ ├── __init__.py │ │ ├── migration_helpers.py │ │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto__add_field_chickenballs_dips.py │ │ └── __init__.py │ │ ├── models.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py └── django18 │ ├── __init__.py │ ├── django18 │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py │ ├── forthewing │ ├── __init__.py │ ├── models.py │ ├── templates │ │ └── forthewing │ │ │ ├── base.html │ │ │ ├── chickenwings_confirm_delete.html │ │ │ ├── chickenwings_created.html │ │ │ ├── chickenwings_deleted.html │ │ │ ├── chickenwings_detail.html │ │ │ ├── chickenwings_form.html │ │ │ ├── chickenwings_list.html │ │ │ └── chickenwings_updated.html │ ├── templatetags │ │ ├── __init__.py │ │ └── chickenwings_tags.py │ ├── tests.py │ ├── urls.py │ └── views.py │ ├── global_assets │ ├── static │ │ ├── css │ │ │ ├── app.css │ │ │ └── style-multiselect.css │ │ ├── js │ │ │ └── script-multiselect.js │ │ └── multiselect-0.9.10 │ │ │ ├── .gitignore │ │ │ ├── LICENSE.txt │ │ │ ├── README.markdown │ │ │ ├── bower.json │ │ │ ├── css │ │ │ └── multi-select.css │ │ │ ├── img │ │ │ └── switch.png │ │ │ ├── js │ │ │ └── jquery.multi-select.js │ │ │ └── multi-select.jquery.json │ └── templates │ │ ├── 404.html │ │ ├── 500.html │ │ ├── app │ │ ├── base.html │ │ └── home.html │ │ └── theme │ │ ├── html5.html │ │ └── site_base.html │ ├── manage.py │ └── pizzagigi │ ├── __init__.py │ ├── admin.py │ ├── models.py │ ├── templates │ └── pizzagigi │ │ ├── base.html │ │ ├── pizza_confirm_delete.html │ │ ├── pizza_created.html │ │ ├── pizza_deleted.html │ │ ├── pizza_detail.html │ │ ├── pizza_form.html │ │ ├── pizza_list.html │ │ └── pizza_updated.html │ ├── templatetags │ ├── __init__.py │ └── pizza_tags.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── test_suite ├── __init__.py ├── settings_for_tests.py ├── test_codecs.py ├── test_forms.py ├── test_models.py ├── test_validators.py └── test_widgets.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | MANIFEST 25 | output.html 26 | 27 | # Installer logs 28 | pip-log.txt 29 | pip-delete-this-directory.txt 30 | 31 | # Unit test / coverage reports 32 | htmlcov/ 33 | .tox/ 34 | .coverage 35 | .cache 36 | nosetests.xml 37 | coverage.xml 38 | 39 | # Translations 40 | *.mo 41 | 42 | # Mr Developer 43 | .mr.developer.cfg 44 | .project 45 | .pydevproject 46 | 47 | # Rope 48 | .ropeproject 49 | 50 | # Django stuff: 51 | *.log 52 | *.pot 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | # Mac 58 | .DS_Store 59 | 60 | # Sqlite dev dbs 61 | *.sqlite 62 | *.sqlite3 63 | -------------------------------------------------------------------------------- /CONTRIBUTORS.rst: -------------------------------------------------------------------------------- 1 | Thanks are due to the following people that have contributed to the software 2 | or the documentation. 3 | 4 | .. figure:: https://avatars0.githubusercontent.com/u/1013698?v=2&s=120 5 | :alt: Gladson Brito 6 | 7 | Gladson Brito 8 | 9 | `Immensa `_ - `Gladson Brito `_ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Kelvin Wong and contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL "KELVIN WONG" BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | 25 | ######## 26 | 27 | 28 | Some code was derived from Django which was licensed under the following terms 29 | 30 | 31 | Copyright (c) Django Software Foundation and individual contributors. 32 | All rights reserved. 33 | 34 | Redistribution and use in source and binary forms, with or without 35 | modification, are permitted provided that the following conditions are met: 36 | 37 | 1. Redistributions of source code must retain the above copyright notice, 38 | this list of conditions and the following disclaimer. 39 | 40 | 2. Redistributions in binary form must reproduce the above copyright 41 | notice, this list of conditions and the following disclaimer in the 42 | documentation and/or other materials provided with the distribution. 43 | 44 | 3. Neither the name of Django nor the names of its contributors may be 45 | used to endorse or promote products derived from this software without 46 | specific prior written permission. 47 | 48 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 49 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 50 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 51 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 52 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 53 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 54 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 55 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 56 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 57 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 58 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.rst 3 | include CONTRIBUTORS.rst 4 | 5 | 6 | recursive-include test_suite *.py 7 | 8 | 9 | recursive-include test_projects *.py 10 | 11 | 12 | recursive-include test_projects/django14 *.py 13 | recursive-include test_projects/django14/django14 *.py 14 | recursive-include test_projects/django14/forthewing *.py 15 | recursive-include test_projects/django14/forthewing/templates/forthewing *.html 16 | recursive-include test_projects/django14/forthewing/templatetags *.py 17 | recursive-include test_projects/django14/global_assets/static/css *.css 18 | recursive-include test_projects/django14/global_assets/static/multiselect-0.9.10 *.txt *.markdown *.json 19 | recursive-include test_projects/django14/global_assets/static/multiselect-0.9.10/css *.css 20 | recursive-include test_projects/django14/global_assets/static/multiselect-0.9.10/img *.png 21 | recursive-include test_projects/django14/global_assets/static/multiselect-0.9.10/js *.js 22 | recursive-include test_projects/django14/global_assets/templates *.html 23 | recursive-include test_projects/django14/global_assets/templates/app *.html 24 | recursive-include test_projects/django14/global_assets/templates/theme *.html 25 | recursive-include test_projects/django14/pizzagigi *.py 26 | recursive-include test_projects/django14/pizzagigi/templates/pizzagigi *.html 27 | recursive-include test_projects/django14/pizzagigi/templatetags *.py 28 | recursive-include test_projects/django14/suthern/ *.py 29 | recursive-include test_projects/django14/suthern/migrations/ *.py 30 | 31 | 32 | recursive-include test_projects/django18 *.py 33 | recursive-include test_projects/django18/django18 *.py 34 | recursive-include test_projects/django18/forthewing *.py 35 | recursive-include test_projects/django18/forthewing/templates/forthewing *.html 36 | recursive-include test_projects/django18/forthewing/templatetags *.py 37 | recursive-include test_projects/django18/global_assets/static/css *.css 38 | recursive-include test_projects/django18/global_assets/static/multiselect-0.9.10 *.txt *.markdown *.json 39 | recursive-include test_projects/django18/global_assets/static/multiselect-0.9.10/css *.css 40 | recursive-include test_projects/django18/global_assets/static/multiselect-0.9.10/img *.png 41 | recursive-include test_projects/django18/global_assets/static/multiselect-0.9.10/js *.js 42 | recursive-include test_projects/django18/global_assets/templates *.html 43 | recursive-include test_projects/django18/global_assets/templates/app *.html 44 | recursive-include test_projects/django18/global_assets/templates/theme *.html 45 | recursive-include test_projects/django18/pizzagigi *.py 46 | recursive-include test_projects/django18/pizzagigi/templates/pizzagigi *.html 47 | recursive-include test_projects/django18/pizzagigi/templatetags *.py 48 | 49 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | **************************** 2 | django-select-multiple-field 3 | **************************** 4 | 5 | Select multiple choices in a single Django model field. Use whenever you want 6 | to store multiple choices in a field without using a many-to-many relation. 7 | 8 | .. figure:: https://github.com/kelvinwong-ca/django-select-multiple-field/raw/master/docs/images/select_multiple_cropped.jpg 9 | 10 | Rendered using the multiselect.js plugin for jQuery [#]_ 11 | 12 | .. [#] jquery.multi-select.js https://github.com/lou/multi-select 13 | 14 | Quick Start 15 | =========== 16 | 17 | .. important:: 18 | 19 | Field attribute ``max_length`` must be set to a value longer than your 20 | longest expected encoded string 21 | 22 | .. important:: 23 | 24 | Choices must be strings, not integers. 25 | 26 | In your models add the select field choices normally:: 27 | 28 | # models.py 29 | 30 | class Pizza(models.Model): 31 | ANCHOVIES = 'a' 32 | BLACK_OLIVES = 'b' 33 | PEPPERONI = 'p' 34 | MOZZARELLA = 'm' 35 | TOPPING_CHOICES = ( 36 | (ANCHOVIES, 'Anchovies'), 37 | (BLACK_OLIVES, 'Black olives'), 38 | (PEPPERONI, 'Pepperoni'), 39 | (MOZZARELLA, 'Mozzarella'), 40 | ) 41 | 42 | toppings = SelectMultipleField( 43 | max_length=10, 44 | choices=TOPPING_CHOICES 45 | ) 46 | 47 | Use a generic view or a modelform as usual. In your template you can use a regular form tag:: 48 | 49 | # template_form.html 50 | 51 |
52 | {{ form.as_p }} 53 | 54 |
55 | 56 | This renders the following HTML:: 57 | 58 | # create.html 59 | 60 |
61 |

62 | 63 | 70 |

71 | 72 |
73 | 74 | Displaying stored choices 75 | ------------------------- 76 | 77 | To display your choices, you will need to decode the field contents. This can 78 | be accomplished with a template tag:: 79 | 80 | # templatetags/pizza_tags.py 81 | 82 | def decode_pie(ingredients): 83 | """ 84 | Decode pizza pie toppings 85 | """ 86 | decoder = dict(Pizza.TOPPING_CHOICES) 87 | decoded = [decoder[t] for t in ingredients] 88 | decoded.sort() 89 | return ', '.join(decoded) 90 | 91 | register.filter('decode_pie', decode_pie) 92 | 93 | In your template you need to import your tags and use them:: 94 | 95 | # details.html 96 | 97 | {% load pizza_tags %} 98 | 99 | {{ pizza.toppings|decode_pie }} 100 | 101 | Encoding the choices 102 | ==================== 103 | 104 | The choices that are selected are stored as comma-delimited text. Consider a 105 | pizza with the following toppings. 106 | 107 | * Pepperoni 108 | * Mozzarella 109 | 110 | This would be stored as a character field as:: 111 | 112 | p,m 113 | 114 | This encoded string is decoded to a Python list using functions in the codecs 115 | module:: 116 | 117 | >>> from select_multiple_field.codecs import * 118 | >>> encoded = 'a,b,c' 119 | >>> decoded = decode_csv_to_list(encoded) 120 | >>> print decoded 121 | [u'a', u'b', u'c'] 122 | >>> print type(decoded) 123 | 124 | 125 | The method of encoding may limit your ability to search for choices. 126 | 127 | Sample application 128 | ================== 129 | 130 | There is a sample application included if you downloaded the tarball. You can try it like this:: 131 | 132 | $ pwd 133 | /home/user/teststuff/django-select-multiple-field 134 | $ cd test_projects/django14 135 | $ python manage.py syncdb 136 | $ python manage.py runserver 137 | 138 | Validating models... 139 | 140 | 0 errors found 141 | Django version 1.4.19, using settings 'django14.settings' 142 | Development server is running at http://127.0.0.1:8000/ 143 | Quit the server with CONTROL-C. 144 | 145 | Troubleshooting 146 | =============== 147 | 148 | Django-select-multiple-field contains two test suites. One is for the field and one is for an implementation of the field in a Django 1.4.19 project. 149 | 150 | You can run the field tests by downloading the tarball and running 'test' in setup.py:: 151 | 152 | $ python setup.py test 153 | 154 | You can run the Django 1.4.19 demo test in a similar manner:: 155 | 156 | $ python setup.py test_demo 157 | 158 | Needless to say you will need to have Django 1.4.19 or later installed. 159 | 160 | Bugs! Help!! 161 | ============ 162 | 163 | If you find any bugs in this software please report them via the Github 164 | issue tracker [#]_ or send an email to code@kelvinwong.ca. Any serious 165 | security bugs should be reported via email only. 166 | 167 | .. [#] Django-select-multiple-field issue tracker https://github.com/kelvinwong-ca/django-select-multiple-field/issues 168 | 169 | Links 170 | ===== 171 | 172 | * https://pypi.python.org/pypi/django-select-multiple-field/ 173 | * https://github.com/kelvinwong-ca/django-select-multiple-field 174 | 175 | Thank-you 176 | ========= 177 | 178 | Thank-you for taking the time to evaluate this software. I appreciate 179 | receiving feedback on your experiences using it and I welcome code 180 | contributions and development ideas. 181 | 182 | http://www.kelvinwong.ca/coders 183 | -------------------------------------------------------------------------------- /docs/images/select_multiple_cropped.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/docs/images/select_multiple_cropped.jpg -------------------------------------------------------------------------------- /docs/images/select_multiple_cropped_src.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/docs/images/select_multiple_cropped_src.png -------------------------------------------------------------------------------- /select_multiple_field/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | 5 | __version__ = '0.4.3' 6 | -------------------------------------------------------------------------------- /select_multiple_field/codecs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.conf import settings 5 | 6 | 7 | DEFAULT_DELIMITER = ',' 8 | 9 | 10 | def decode_csv_to_list(encoded): 11 | """ 12 | Decodes a delimiter separated string to a Python list 13 | """ 14 | delimiter = getattr( 15 | settings, 'SELECTMULTIPLEFIELD_DELIMITER', DEFAULT_DELIMITER) 16 | if encoded == '': 17 | return [] 18 | 19 | decoded = sorted(set(encoded.split(delimiter))) 20 | return decoded 21 | 22 | 23 | def encode_list_to_csv(decoded): 24 | """ 25 | Encodes a Python list to a delimiter separated string 26 | 27 | Note: This sorts the list lexicographically 28 | """ 29 | delimiter = getattr( 30 | settings, 'SELECTMULTIPLEFIELD_DELIMITER', DEFAULT_DELIMITER) 31 | decoded = sorted(set(decoded)) 32 | return delimiter.join(decoded) 33 | -------------------------------------------------------------------------------- /select_multiple_field/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.conf import settings 5 | from django.core import validators 6 | from django.forms import fields 7 | from django.utils import six 8 | 9 | from .codecs import decode_csv_to_list 10 | from .widgets import SelectMultipleField 11 | 12 | 13 | DEFAULT_DELIMITER = ',' 14 | DEFAULT_MAX_CHOICES_ATTR = 'data-max-choices' 15 | 16 | 17 | class SelectMultipleFormField(fields.MultipleChoiceField): 18 | 19 | widget = SelectMultipleField 20 | 21 | def __init__( 22 | self, max_length=None, size=4, max_choices=None, 23 | max_choices_attr=DEFAULT_MAX_CHOICES_ATTR, 24 | *args, **kwargs): 25 | """ 26 | max_length refers to number of characters used to store the encoded 27 | list of choices (est. 2n - 1) 28 | 29 | size is the HTML element size attribute passed to the widget 30 | 31 | max_choices is the maximum number of choices allowed by the field 32 | 33 | max_choices_attr is a string used as an attribute name in the widget 34 | representation of max_choices (currently a data attribute) 35 | 36 | coerce is bound to ModelField.to_python method when using 37 | ModelViewMixin, otherwise it returns what is passed (the identity 38 | function) 39 | 40 | empty_value is the value used to represent an empty field 41 | """ 42 | self.max_length, self.max_choices = max_length, max_choices 43 | self.size, self.max_choices_attr = size, max_choices_attr 44 | self.coerce = kwargs.pop('coerce', lambda val: val) 45 | self.empty_value = kwargs.pop('empty_value', []) 46 | if not hasattr(self, 'empty_values'): 47 | self.empty_values = list(validators.EMPTY_VALUES) 48 | super(SelectMultipleFormField, self).__init__(*args, **kwargs) 49 | 50 | def to_python(self, value): 51 | """ 52 | Takes processed widget data as value, possibly a char string, and 53 | makes it into a Python list 54 | 55 | Returns a Python list 56 | 57 | Method also handles lists and strings 58 | """ 59 | if (value == self.empty_value) or (value in self.empty_values): 60 | return self.empty_value 61 | 62 | if isinstance(value, six.string_types): 63 | if len(value) == 0: 64 | return [] 65 | 66 | native = decode_csv_to_list(value) 67 | return native 68 | 69 | return list(value) 70 | 71 | def _coerce(self, value): 72 | return value 73 | 74 | def get_prep_value(self, value): 75 | """ 76 | Prepares a string for use in serializer 77 | """ 78 | delimiter = getattr( 79 | settings, 'SELECTMULTIPLEFIELD_DELIMITER', DEFAULT_DELIMITER) 80 | if isinstance(value, (list, tuple)): 81 | if len(value) == 0: 82 | return '' 83 | else: 84 | return delimiter.join(value) 85 | 86 | return '' 87 | 88 | # def validate(self, value): 89 | # checked_out = True 90 | # for val in value: 91 | # if not self.valid_value(val): 92 | # checked_out = False 93 | # 94 | # return super(SelectMultipleFormField, self).validate(value) 95 | 96 | def widget_attrs(self, widget): 97 | """ 98 | Given a Widget instance (*not* a Widget class), returns a dictionary of 99 | any HTML attributes that should be added to the Widget, based on this 100 | Field. 101 | """ 102 | attrs = super(SelectMultipleFormField, self).widget_attrs(widget) 103 | if self.size != 4: 104 | attrs.update({'size': str(self.size)}) 105 | 106 | if self.max_choices: 107 | attrs.update({self.max_choices_attr: str(self.max_choices)}) 108 | 109 | return attrs 110 | -------------------------------------------------------------------------------- /select_multiple_field/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.core import exceptions, validators 5 | from django.db import models 6 | from django.utils import six 7 | from django.utils.encoding import force_text, python_2_unicode_compatible 8 | from django.utils.text import capfirst 9 | from django.utils.translation import ugettext_lazy as _ 10 | 11 | from .codecs import decode_csv_to_list, encode_list_to_csv 12 | from .validators import MaxChoicesValidator, MaxLengthValidator 13 | import select_multiple_field.forms as forms 14 | 15 | 16 | DEFAULT_DELIMITER = ',' 17 | 18 | 19 | @python_2_unicode_compatible 20 | class SelectMultipleField(six.with_metaclass(models.SubfieldBase, 21 | models.Field)): 22 | """Stores multiple selection choices as serialized list""" 23 | 24 | default_error_messages = { 25 | 'blank': _("This field cannot be blank."), 26 | 'invalid_type': _( 27 | "Types passed as value must be string, list, tuple or None, " 28 | "not '%(value)s'."), 29 | 'invalid_choice': _( 30 | "Select a valid choice. %(value)s is not one of the available " 31 | "choices."), 32 | 'null': _("This field cannot be null."), 33 | } 34 | description = _('Select multiple field') 35 | 36 | def __init__(self, *args, **kwargs): 37 | """ 38 | SelectMultipleField rejects items with no answer by default 39 | 40 | By default responses are required, so 'blank' is False 41 | """ 42 | if 'max_choices' in kwargs: 43 | self.max_choices = kwargs.pop('max_choices') 44 | 45 | if 'include_blank' in kwargs: 46 | self.include_blank = kwargs.pop('include_blank') 47 | 48 | super(SelectMultipleField, self).__init__(*args, **kwargs) 49 | 50 | self.validators.append(MaxLengthValidator(self.max_length)) 51 | if hasattr(self, 'max_choices'): 52 | self.validators.append(MaxChoicesValidator(self.max_choices)) 53 | 54 | def __str__(self): 55 | return "%s" % force_text(self.description) 56 | 57 | def get_internal_type(self): 58 | return "CharField" 59 | 60 | def to_python(self, value): 61 | """ 62 | When SelectMultipleField is assigned a value, this method coerces 63 | into a list usable by Python 64 | 65 | value is Encoded strings from the database or Python native types in 66 | need of validation 67 | 68 | Raises ValidationError if value is not in choices or if invalid type 69 | 70 | Returns list 71 | """ 72 | if value is None: 73 | return value 74 | 75 | elif isinstance(value, (list, tuple)): 76 | self.validate_options_list(value) 77 | return value 78 | 79 | elif isinstance(value, six.string_types): 80 | # 81 | # Strings are always encoded choices 82 | # 83 | native = decode_csv_to_list(value) 84 | return native 85 | 86 | msg = self.error_messages['invalid_type'] % {'value': type(value)} 87 | raise exceptions.ValidationError(msg) 88 | 89 | def get_prep_value(self, value): 90 | """ 91 | Perform preliminary non-db specific value checks and conversions. 92 | 93 | This takes a Python list and encodes it into a form storable in the 94 | database 95 | 96 | Returns a string or None 97 | """ 98 | if value is None: 99 | return None 100 | 101 | return encode_list_to_csv(value) 102 | 103 | def get_choices(self, **kwargs): 104 | """ 105 | Choices from model without initial blank choices 106 | 107 | ie Stop widget from producing 108 | 109 | If ModelField.include_blank is set then ignore any overrides sent via 110 | kwargs 111 | """ 112 | include_blank = False 113 | if hasattr(self, 'include_blank'): 114 | include_blank = self.include_blank 115 | if 'include_blank' in kwargs: 116 | kwargs.pop('include_blank') 117 | 118 | field_options = { 119 | 'include_blank': include_blank 120 | } 121 | field_options.update(kwargs) 122 | return super(SelectMultipleField, self).get_choices(**field_options) 123 | 124 | def has_choices(self): 125 | """ 126 | Check if the field has choices values bound to it 127 | """ 128 | choices = getattr(self, 'choices', None) 129 | if choices is None: 130 | choices = getattr(self, '_choices', None) 131 | 132 | return bool(choices) 133 | 134 | def value_to_string(self, obj): 135 | """ 136 | Used for serialization of the expected Python list 137 | """ 138 | native = self._get_val_from_obj(obj) 139 | return native 140 | # return smart_text(self._get_val_from_obj(obj)) # Default code 141 | 142 | def validate(self, value, model_instance): 143 | """ 144 | Validates value and throws ValidationError. Subclasses should override 145 | this to provide validation logic. 146 | """ 147 | if not self.editable: 148 | # Skip validation for non-editable fields. 149 | return 150 | 151 | if self.has_choices() and value: 152 | if isinstance(value, (list, tuple)): 153 | bad_values = [] 154 | for opt in value: 155 | if self.blank and opt in validators.EMPTY_VALUES: 156 | pass 157 | elif opt not in self.get_choices_keys(): 158 | bad_values.append(opt) 159 | if len(bad_values) == 0: 160 | return 161 | else: 162 | msg = self.error_messages['invalid_choice'] % { 163 | 'value': bad_values} 164 | raise exceptions.ValidationError(msg) 165 | 166 | msg = self.error_messages['invalid_choice'] % {'value': value} 167 | raise exceptions.ValidationError(msg) 168 | 169 | if value is None and not self.null: 170 | raise exceptions.ValidationError(self.error_messages['null']) 171 | 172 | if not self.blank and value in validators.EMPTY_VALUES: 173 | raise exceptions.ValidationError(self.error_messages['blank']) 174 | 175 | def validate_options_list(self, value): 176 | """ 177 | Checks that all options in value list are in choices 178 | 179 | Raises ValidationError if an option in value list is not in choices 180 | 181 | Returns None if all values are in choices 182 | """ 183 | for option in value: 184 | if not self.validate_option(option): 185 | msg = self.error_messages['invalid_choice'] % {'value': option} 186 | raise exceptions.ValidationError(msg) 187 | 188 | return 189 | 190 | def get_choices_keys(self, **kwargs): 191 | """ 192 | Flattens choices and optgroup choices into a plain list of keys 193 | 194 | Returns choices keys as list 195 | """ 196 | flat_choices = [] 197 | choices = self.get_choices(**kwargs) 198 | for key, val in choices: 199 | if isinstance(val, (list, tuple)): 200 | for opt_key, opt_val in val: 201 | flat_choices.append(opt_key) 202 | else: 203 | flat_choices.append(key) 204 | 205 | return flat_choices 206 | 207 | def validate_option(self, value): 208 | """ 209 | Checks that value is in choices 210 | """ 211 | if self.blank and value in validators.EMPTY_VALUES: 212 | return True 213 | 214 | flat_choices = self.get_choices_keys() 215 | return value in flat_choices 216 | 217 | def formfield(self, **kwargs): 218 | """ 219 | This returns the correct formclass without calling super 220 | 221 | Returns select_multiple_field.forms.SelectMultipleFormField 222 | """ 223 | defaults = {'required': not self.blank, 224 | 'label': capfirst(self.verbose_name), 225 | 'help_text': self.help_text} 226 | if self.has_default(): 227 | if callable(self.default): 228 | defaults['initial'] = self.default 229 | defaults['show_hidden_initial'] = True 230 | else: 231 | defaults['initial'] = self.get_default() 232 | 233 | if self.choices: 234 | # Django normally includes an empty choice if blank, has_default 235 | # and initial are all False, we are intentially breaking this 236 | # convention 237 | include_blank = self.blank 238 | defaults['choices'] = self.get_choices(include_blank=include_blank) 239 | defaults['coerce'] = self.to_python 240 | if self.null: 241 | defaults['empty_value'] = None 242 | 243 | # Many of the subclass-specific formfield arguments (min_value, 244 | # max_value) don't apply for choice fields, so be sure to only pass 245 | # the values that SelectMultipleFormField will understand. 246 | for k in kwargs.keys(): 247 | if k not in ('coerce', 'empty_value', 'choices', 'required', 248 | 'widget', 'label', 'initial', 'help_text', 249 | 'error_messages', 'show_hidden_initial'): 250 | del kwargs[k] 251 | 252 | defaults.update(kwargs) 253 | return forms.SelectMultipleFormField(**defaults) 254 | 255 | def south_field_triple(self): 256 | try: 257 | from south.modelsinspector import introspector 258 | except ImportError: 259 | pass 260 | else: 261 | cls_name = '{}.{}'.format( 262 | self.__class__.__module__, 263 | self.__class__.__name__) 264 | args, kwargs = introspector(self) 265 | 266 | if hasattr(self, 'max_choices'): 267 | kwargs["max_choices"] = self.max_choices 268 | 269 | if hasattr(self, 'include_blank'): 270 | kwargs["include_blank"] = self.include_blank 271 | 272 | return (cls_name, args, kwargs) 273 | 274 | def deconstruct(self): 275 | """ 276 | How to reduce the field to a serializable form. 277 | 278 | The arguments to pass to field constructor to reconstruct it. 279 | 280 | Returns a tuple of four items: 281 | the field’s attribute name, 282 | the full import path of the field class, 283 | the positional arguments (an empty list in this case), 284 | the keyword arguments (as a dict). 285 | """ 286 | name, path, args, kwargs = super( 287 | SelectMultipleField, self).deconstruct() 288 | 289 | if hasattr(self, 'max_choices'): 290 | kwargs["max_choices"] = self.max_choices 291 | 292 | if hasattr(self, 'include_blank'): 293 | kwargs["include_blank"] = self.include_blank 294 | 295 | return ( 296 | force_text(name, strings_only=True), 297 | path, 298 | args, 299 | kwargs, 300 | ) 301 | -------------------------------------------------------------------------------- /select_multiple_field/validators.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.core import validators 5 | from django.utils.encoding import force_text 6 | from django.utils.translation import ungettext_lazy 7 | 8 | try: 9 | from django.utils.deconstruct import deconstructible 10 | except ImportError: 11 | def deconstructible(x): 12 | return x 13 | 14 | from .codecs import encode_list_to_csv 15 | 16 | 17 | @deconstructible 18 | class MaxChoicesValidator(validators.BaseValidator): 19 | 20 | message = ungettext_lazy( 21 | 'Ensure this value has at most %(limit_value)d choice (it has %(show_value)d).', # NOQA 22 | 'Ensure this value has at most %(limit_value)d choices (it has %(show_value)d).', # NOQA 23 | 'limit_value') 24 | code = 'max_choices' 25 | 26 | def compare(self, a, b): 27 | return a > b 28 | 29 | def clean(self, x): 30 | return len(x) 31 | 32 | 33 | @deconstructible 34 | class MaxLengthValidator(validators.BaseValidator): 35 | 36 | message = ungettext_lazy( 37 | 'Ensure this value has at most %(limit_value)d character (it has %(show_value)d).', # NOQA 38 | 'Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).', # NOQA 39 | 'limit_value') 40 | code = 'max_length' 41 | 42 | def compare(self, a, b): 43 | return a > b 44 | 45 | def clean(self, value): 46 | return len(force_text(encode_list_to_csv(value))) 47 | -------------------------------------------------------------------------------- /select_multiple_field/widgets.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.forms import widgets 5 | from django.utils.safestring import mark_safe 6 | 7 | try: 8 | from django.forms.utils import flatatt 9 | except ImportError: 10 | from django.forms.util import flatatt 11 | 12 | try: 13 | from django.utils.html import format_html 14 | except ImportError: 15 | def format_html(format_string, *args, **kwargs): 16 | return format_string.format(*args, **kwargs) 17 | 18 | 19 | HTML_ATTR_CLASS = 'select-multiple-field' 20 | 21 | 22 | class SelectMultipleField(widgets.SelectMultiple): 23 | """Multiple select widget ready for jQuery multiselect.js""" 24 | 25 | allow_multiple_selected = True 26 | 27 | def render(self, name, value, attrs={}, choices=()): 28 | rendered_attrs = {'class': HTML_ATTR_CLASS} 29 | rendered_attrs.update(attrs) 30 | if value is None: 31 | value = [] 32 | 33 | final_attrs = self.build_attrs(rendered_attrs, name=name) 34 | # output = [u'', 36 | flatatt(final_attrs))] 37 | options = self.render_options(choices, value) 38 | if options: 39 | output.append(options) 40 | 41 | output.append('') 42 | return mark_safe('\n'.join(output)) 43 | 44 | def value_from_datadict(self, data, files, name): 45 | """ 46 | SelectMultipleField widget delegates processing of raw user data to 47 | Django's SelectMultiple widget 48 | 49 | Returns list or None 50 | """ 51 | return super(SelectMultipleField, self).value_from_datadict( 52 | data, files, name) 53 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from distutils.core import setup, Command 3 | import os 4 | import re 5 | import sys 6 | 7 | from select_multiple_field import __version__ 8 | 9 | 10 | cmdclasses = dict() 11 | README_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), 12 | 'README.rst') 13 | long_description = open(README_PATH, 'r').read() 14 | 15 | 16 | class DemoTester(Command): 17 | """Runs demonstration project tests""" 18 | 19 | user_options = [] 20 | test_settings = { 21 | '1.4': 'test_projects.django14.django14.settings', 22 | '1.5': 'test_projects.django14.django14.settings', 23 | '1.6': 'test_projects.django14.django14.settings', 24 | '1.7': 'test_projects.django14.django14.settings', 25 | '1.8': 'test_projects.django18.django18.settings', 26 | '1.9': 'test_projects.django18.django18.settings', 27 | } 28 | 29 | def initialize_options(self): 30 | pass 31 | 32 | def finalize_options(self): 33 | pass 34 | 35 | def run(self): 36 | sys.dont_write_bytecode = True 37 | from django import get_version 38 | django_release = re.search(r'^\d\.\d', get_version()).group(0) 39 | test_settings_exist = django_release in self.test_settings.keys() 40 | try: 41 | dj_ver = [int(n) for n in re.split(r'[.ab]', get_version())] 42 | except ValueError: 43 | # Pre-release Djangos must be testable!!! 44 | dj_too_old = False 45 | else: 46 | dj_too_old = dj_ver < [1, 4, 2] 47 | 48 | if test_settings_exist is False or dj_too_old: 49 | print("Please install Django 1.4.19 - 1.9 to run the test suite") 50 | exit(-1) 51 | os.environ['DJANGO_SETTINGS_MODULE'] = self.test_settings[ 52 | django_release] 53 | try: 54 | from django.core.management import call_command 55 | except ImportError: 56 | print("Please install Django 1.4.19 - 1.9 to run the test suite") 57 | exit(-1) 58 | 59 | import django 60 | if hasattr(django, 'setup'): 61 | django.setup() 62 | 63 | call_command('test', 'pizzagigi', interactive=False, verbosity=1) 64 | call_command('test', 'forthewing', interactive=False, verbosity=1) 65 | 66 | try: 67 | import south 68 | except ImportError: 69 | pass 70 | else: 71 | call_command('test', 'suthern', interactive=False, verbosity=1) 72 | 73 | 74 | cmdclasses['test_demo'] = DemoTester 75 | 76 | 77 | class Tester(Command): 78 | """Runs project unit tests""" 79 | 80 | user_options = [] 81 | 82 | def initialize_options(self): 83 | pass 84 | 85 | def finalize_options(self): 86 | pass 87 | 88 | def run(self): 89 | sys.dont_write_bytecode = True 90 | os.environ['DJANGO_SETTINGS_MODULE'] = 'test_suite.settings_for_tests' 91 | import django 92 | if hasattr(django, 'setup'): 93 | django.setup() 94 | 95 | try: 96 | from django.utils.unittest import TextTestRunner, defaultTestLoader 97 | except ImportError: 98 | from unittest import TextTestRunner, defaultTestLoader 99 | 100 | from test_suite import ( 101 | test_codecs, test_forms, test_models, test_validators, 102 | test_widgets) 103 | suite = defaultTestLoader.loadTestsFromModule(test_codecs) 104 | suite.addTests(defaultTestLoader.loadTestsFromModule(test_forms)) 105 | suite.addTests(defaultTestLoader.loadTestsFromModule(test_models)) 106 | suite.addTests(defaultTestLoader.loadTestsFromModule(test_validators)) 107 | suite.addTests(defaultTestLoader.loadTestsFromModule(test_widgets)) 108 | runner = TextTestRunner() 109 | result = runner.run(suite) 110 | if result.wasSuccessful() is not True: 111 | raise SystemExit(int(bool(result.errors or result.failures))) 112 | 113 | cmdclasses['test'] = Tester 114 | 115 | setup( 116 | name='django-select-multiple-field', 117 | description='Select multiple choices in a single Django model field', 118 | long_description=long_description, 119 | version=__version__, 120 | license='BSD', 121 | keywords=[ 122 | 'select', 'select multiple', 'Django', 'model-field', 123 | 'Django-Select-Multiple-Field'], 124 | author='Kelvin Wong', 125 | author_email='code@kelvinwong.ca', 126 | url='https://github.com/kelvinwong-ca/django-select-multiple-field', 127 | classifiers=['Development Status :: 3 - Alpha', 128 | # 'Development Status :: 4 - Beta', 129 | 'Environment :: Web Environment', 130 | 'Framework :: Django', 131 | 'Intended Audience :: Developers', 132 | 'License :: OSI Approved :: BSD License', 133 | 'Operating System :: OS Independent', 134 | 'Programming Language :: Python', 135 | 'Programming Language :: Python :: 2.7', 136 | 'Programming Language :: Python :: 3', 137 | 'Programming Language :: Python :: 3.2', 138 | 'Programming Language :: Python :: 3.3', 139 | 'Programming Language :: Python :: 3.4', 140 | 'Programming Language :: Python :: 3.5', 141 | 'Topic :: Internet :: WWW/HTTP'], 142 | packages=['select_multiple_field'], 143 | cmdclass=cmdclasses 144 | ) 145 | -------------------------------------------------------------------------------- /test_projects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_projects/__init__.py -------------------------------------------------------------------------------- /test_projects/django14/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_projects/django14/__init__.py -------------------------------------------------------------------------------- /test_projects/django14/django14/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_projects/django14/django14/__init__.py -------------------------------------------------------------------------------- /test_projects/django14/django14/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 5 | MODULE_DIR = os.path.join(os.path.dirname(os.path.dirname(BASE_DIR))) 6 | 7 | sys.path.append(BASE_DIR) 8 | sys.path.append(MODULE_DIR) 9 | 10 | DEBUG = True 11 | TEMPLATE_DEBUG = DEBUG 12 | 13 | ADMINS = ( 14 | ('Kelvin', 'code@kelvinwong.ca'), 15 | ) 16 | 17 | MANAGERS = ADMINS 18 | 19 | DATABASES = { 20 | 'default': { 21 | 'ENGINE': 'django.db.backends.sqlite3', 22 | # 'NAME': ':memory:', 23 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 24 | 'USER': '', 25 | 'PASSWORD': '', 26 | 'HOST': '', 27 | 'PORT': '', 28 | } 29 | } 30 | 31 | # Local time zone for this installation. Choices can be found here: 32 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 33 | # although not all choices may be available on all operating systems. 34 | # In a Windows environment this must be set to your system time zone. 35 | TIME_ZONE = 'America/Chicago' 36 | 37 | # Language code for this installation. All choices can be found here: 38 | # http://www.i18nguy.com/unicode/language-identifiers.html 39 | LANGUAGE_CODE = 'en' 40 | 41 | SITE_ID = 1 42 | 43 | # If you set this to False, Django will make some optimizations so as not 44 | # to load the internationalization machinery. 45 | USE_I18N = True 46 | 47 | # If you set this to False, Django will not format dates, numbers and 48 | # calendars according to the current locale. 49 | USE_L10N = True 50 | 51 | ugettext = lambda s: s 52 | LANGUAGES = ( 53 | ('en', ugettext('English')), 54 | ('fr', ugettext('French')), 55 | ) 56 | 57 | # If you set this to False, Django will not use timezone-aware datetimes. 58 | USE_TZ = True 59 | 60 | # Absolute filesystem path to the directory that will hold user-uploaded files. 61 | # Example: "/home/media/media.lawrence.com/media/" 62 | MEDIA_ROOT = '' 63 | 64 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 65 | # trailing slash. 66 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" 67 | MEDIA_URL = '' 68 | 69 | # Absolute path to the directory static files should be collected to. 70 | # Don't put anything in this directory yourself; store your static files 71 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 72 | # Example: "/home/media/media.lawrence.com/static/" 73 | STATIC_ROOT = os.path.join(BASE_DIR, 'site_media', 'static') 74 | 75 | # URL prefix for static files. 76 | # Example: "http://media.lawrence.com/static/" 77 | STATIC_URL = '/static/' 78 | 79 | # Additional locations of static files 80 | STATICFILES_DIRS = ( 81 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 82 | # Always use forward slashes, even on Windows. 83 | # Don't forget to use absolute paths, not relative paths. 84 | os.path.join(BASE_DIR, 'global_assets', 'static'), 85 | ) 86 | 87 | # List of finder classes that know how to find static files in 88 | # various locations. 89 | STATICFILES_FINDERS = ( 90 | 'django.contrib.staticfiles.finders.FileSystemFinder', 91 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 92 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder', 93 | ) 94 | 95 | # Make this unique, and don't share it with anybody. 96 | SECRET_KEY = '-iq#uxq#-ae0*(aq&5)j0w()pjd47$wvw&*0w*hix+vik#=_^$' 97 | 98 | # List of callables that know how to import templates from various sources. 99 | TEMPLATE_LOADERS = ( 100 | 'django.template.loaders.filesystem.Loader', 101 | 'django.template.loaders.app_directories.Loader', 102 | # 'django.template.loaders.eggs.Loader', 103 | ) 104 | 105 | MIDDLEWARE_CLASSES = ( 106 | 'django.middleware.common.CommonMiddleware', 107 | 'django.contrib.sessions.middleware.SessionMiddleware', 108 | 'django.middleware.csrf.CsrfViewMiddleware', 109 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 110 | 'django.contrib.messages.middleware.MessageMiddleware', 111 | 'django.middleware.locale.LocaleMiddleware', 112 | # Uncomment the next line for simple clickjacking protection: 113 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware', 114 | ) 115 | 116 | ROOT_URLCONF = 'django14.urls' 117 | 118 | # Python dotted path to the WSGI application used by Django's runserver. 119 | WSGI_APPLICATION = 'django14.wsgi.application' 120 | 121 | TEMPLATE_DIRS = ( 122 | # Put strings here, like "/home/html/django_templates" 123 | # Always use forward slashes, even on Windows. 124 | # Don't forget to use absolute paths, not relative paths. 125 | os.path.join(BASE_DIR, 'global_assets', 'templates'), 126 | ) 127 | 128 | INSTALLED_APPS = [ 129 | 'select_multiple_field', 130 | 'pizzagigi', 131 | 'forthewing', 132 | 133 | 'django.contrib.auth', 134 | 'django.contrib.contenttypes', 135 | 'django.contrib.sessions', 136 | 'django.contrib.sites', 137 | 'django.contrib.messages', 138 | 'django.contrib.staticfiles', 139 | # Uncomment the next line to enable the admin: 140 | 'django.contrib.admin', 141 | # Uncomment the next line to enable admin documentation: 142 | # 'django.contrib.admindocs', 143 | ] 144 | 145 | SOUTH_APPS = [ 146 | 'south', 147 | 'suthern', 148 | ] 149 | 150 | try: 151 | import south 152 | except ImportError: 153 | pass 154 | else: 155 | INSTALLED_APPS.extend(SOUTH_APPS) 156 | 157 | # A sample logging configuration. The only tangible logging 158 | # performed by this configuration is to send an email to 159 | # the site admins on every HTTP 500 error when DEBUG=False. 160 | # See http://docs.djangoproject.com/en/dev/topics/logging for 161 | # more details on how to customize your logging configuration. 162 | LOGGING = { 163 | 'version': 1, 164 | 'disable_existing_loggers': False, 165 | 'filters': { 166 | 'require_debug_false': { 167 | '()': 'django.utils.log.RequireDebugFalse' 168 | } 169 | }, 170 | 'handlers': { 171 | 'mail_admins': { 172 | 'level': 'ERROR', 173 | 'filters': ['require_debug_false'], 174 | 'class': 'django.utils.log.AdminEmailHandler' 175 | } 176 | }, 177 | 'loggers': { 178 | 'django.request': { 179 | 'handlers': ['mail_admins'], 180 | 'level': 'ERROR', 181 | 'propagate': True, 182 | }, 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /test_projects/django14/django14/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from django.views.generic import TemplateView 3 | 4 | from django.contrib import admin 5 | admin.autodiscover() 6 | 7 | urlpatterns = patterns('', # NOQA 8 | 9 | url(r'^$', TemplateView.as_view( 10 | template_name='app/home.html'), name='home'), 11 | url(r'^pizzagigi/', include('pizzagigi.urls', namespace='pizza')), 12 | url(r'^forthewing/', include('forthewing.urls', namespace='ftw')), 13 | 14 | url(r'^i18n/', include('django.conf.urls.i18n')), 15 | 16 | # Uncomment the next line to enable the admin: 17 | url(r'^admin/', include(admin.site.urls)), 18 | ) 19 | -------------------------------------------------------------------------------- /test_projects/django14/django14/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for django14 project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | 18 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django14.settings") 19 | 20 | # This application object is used by any WSGI server configured to use this 21 | # file. This includes Django's development server, if the WSGI_APPLICATION 22 | # setting points here. 23 | from django.core.wsgi import get_wsgi_application 24 | application = get_wsgi_application() 25 | 26 | # Apply WSGI middleware here. 27 | # from helloworld.wsgi import HelloWorldApplication 28 | # application = HelloWorldApplication(application) 29 | -------------------------------------------------------------------------------- /test_projects/django14/forthewing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_projects/django14/forthewing/__init__.py -------------------------------------------------------------------------------- /test_projects/django14/forthewing/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.core.urlresolvers import reverse 5 | from django.db import models 6 | from django.utils.encoding import ( 7 | force_text, python_2_unicode_compatible) 8 | from django.utils.translation import ugettext_lazy as _ 9 | 10 | from select_multiple_field.models import SelectMultipleField 11 | 12 | 13 | @python_2_unicode_compatible 14 | class ChickenWings(models.Model): 15 | """ChickenWings demonstrates optgroup usage and max_choices""" 16 | 17 | SUICIDE = 's' 18 | HOT = 'h' 19 | MEDIUM = 'm' 20 | MILD = 'M' 21 | CAJUN = 'c' 22 | JERK = 'j' 23 | HONEY_GARLIC = 'g' 24 | HONEY_BBQ = 'H' 25 | THAI = 't' 26 | BACON = 'b' 27 | BOURBON = 'B' 28 | FLAVOUR_CHOICES = ( 29 | (_('Hot & Spicy'), ( 30 | (SUICIDE, _('Suicide hot')), 31 | (HOT, _('Hot hot sauce')), 32 | (MEDIUM, _('Medium hot sauce')), 33 | (MILD, _('Mild hot sauce')), 34 | (CAJUN, _('Cajun sauce')), 35 | (JERK, _('Jerk sauce')))), 36 | (_('Sweets'), ( 37 | (HONEY_GARLIC, _('Honey garlic')), 38 | (HONEY_BBQ, _('Honey barbeque')), 39 | (THAI, _('Thai sweet sauce')), 40 | (BACON, _('Messy bacon sauce')), 41 | (BOURBON, _('Bourbon whiskey barbeque')))), 42 | ) 43 | flavour = SelectMultipleField( 44 | blank=True, 45 | include_blank=False, 46 | max_length=5, 47 | max_choices=2, 48 | choices=FLAVOUR_CHOICES 49 | ) 50 | 51 | def __str__(self): 52 | return "pk=%s" % force_text(self.pk) 53 | 54 | def get_absolute_url(self): 55 | return reverse('ftw:detail', args=[self.pk]) 56 | 57 | 58 | def show_flavour(flavour): 59 | """ 60 | Decode flavour to full name 61 | 62 | This supports both plain choices and optgroup choices 63 | """ 64 | decoder = dict() 65 | for c in ChickenWings.FLAVOUR_CHOICES: 66 | if isinstance(c[1], (tuple, list)): 67 | for k, v in c[1]: 68 | decoder[k] = v 69 | else: 70 | decoder[c[0]] = c[1] 71 | 72 | if flavour in decoder: 73 | return force_text(decoder[flavour]) 74 | else: 75 | return force_text('') 76 | -------------------------------------------------------------------------------- /test_projects/django14/forthewing/templates/forthewing/base.html: -------------------------------------------------------------------------------- 1 | {% extends "app/base.html" %} 2 | {% load url from future %} 3 | {% load i18n static %} 4 | 5 | 6 | {% block page_title %}{% trans 'Chicken wings demo' %}{% endblock %} 7 | 8 | 9 | {% block messaging_extra %} 10 | 11 | {% if form.errors %} 12 |
13 |
14 |
15 | {% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %} 16 |
17 |
18 |
19 | {% endif %} 20 | 21 | {% endblock %} 22 | 23 | 24 | {% block breadcrumbs %} 25 | 26 | 30 | 31 | {% endblock %} 32 | 33 | {% block body %} 34 | 35 | 36 | {% block content %}{% endblock %} 37 | 38 | 39 | {% endblock %} 40 | -------------------------------------------------------------------------------- /test_projects/django14/forthewing/templates/forthewing/chickenwings_confirm_delete.html: -------------------------------------------------------------------------------- 1 | {% extends "forthewing/base.html" %} 2 | {% load url from future %} 3 | {% load i18n static %} 4 | 5 | 6 | {% block page_title %}{% trans 'Confirm delete' %}{% endblock %} 7 | 8 | 9 | {% block breadcrumbs %} 10 | 11 | 17 | 18 | {% endblock %} 19 | 20 | 21 | {% block content %} 22 | 23 |

{% trans 'Delete chicken wings order' %} #{{ chickenwings.id }}

24 | 25 |

{% trans 'Your order will be deleted' %}

26 | 27 |
28 | {% csrf_token %} 29 | 30 |
31 | 32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /test_projects/django14/forthewing/templates/forthewing/chickenwings_created.html: -------------------------------------------------------------------------------- 1 | {% extends "forthewing/base.html" %} 2 | {% load url from future %} 3 | {% load i18n static %} 4 | 5 | 6 | {% block page_title %}{% trans 'Chicken wings created' %}{% endblock %} 7 | 8 | 9 | {% block breadcrumbs %} 10 | 11 | 16 | 17 | {% endblock %} 18 | 19 | 20 | {% block content %} 21 | 22 |

{% trans 'Chicken wings created' %}

23 | 24 | 25 |

{% trans 'Your Chicken wings were created' %}

26 | 27 | {% trans 'View chicken wings' %} 28 | 29 | {% endblock %} -------------------------------------------------------------------------------- /test_projects/django14/forthewing/templates/forthewing/chickenwings_deleted.html: -------------------------------------------------------------------------------- 1 | {% extends "forthewing/base.html" %} 2 | {% load url from future %} 3 | {% load i18n static %} 4 | 5 | 6 | {% block page_title %}{% trans 'Order deleted' %}{% endblock %} 7 | 8 | 9 | {% block breadcrumbs %} 10 | 11 | 16 | 17 | {% endblock %} 18 | 19 | 20 | {% block content %} 21 | 22 |

{% trans 'Order deleted' %}

23 | 24 | 25 |

{% trans 'Your chicken wings order was deleted' %}

26 | 27 | {% trans 'View orders' %} 28 | 29 | {% endblock %} -------------------------------------------------------------------------------- /test_projects/django14/forthewing/templates/forthewing/chickenwings_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "forthewing/base.html" %} 2 | {% load url from future %} 3 | {% load i18n static %} 4 | {% load chickenwings_tags %} 5 | 6 | 7 | {% block page_title %}{% trans 'Chicken wings order' %} #{{ chickenwings.id }}{% endblock %} 8 | 9 | 10 | {% block breadcrumbs %} 11 | 12 | 17 | 18 | {% endblock %} 19 | 20 | 21 | {% block content %} 22 | 23 |

{% trans 'Chicken wings order' %} #{{ chickenwings.id }}

24 | 25 |

{% trans 'The chicken wings have been flavoured with the following sauces.' %}

26 | 27 |
    28 | {% for flavour in chickenwings.flavour %} 29 |
  • {{ flavour|decode_flavour }}
  • 30 | {% empty %} 31 |
  • {% trans 'No sauce! Whaaa?' %} 32 | {% endfor %} 33 |
34 | 35 | 40 | 41 | {% endblock %} -------------------------------------------------------------------------------- /test_projects/django14/forthewing/templates/forthewing/chickenwings_form.html: -------------------------------------------------------------------------------- 1 | {% extends "forthewing/base.html" %} 2 | {% load url from future %} 3 | {% load i18n static %} 4 | 5 | 6 | {% block page_title %}{% if chickenwings.id %}{% trans 'Update chicken wings' %} #{{ chickenwings.id }}{% else %}{% trans 'Create chicken wings' %}{% endif %}{% endblock %} 7 | 8 | 9 | {% block extra_head %} 10 | 11 | 12 | 13 | {% endblock %} 14 | 15 | 16 | {% block breadcrumbs %} 17 | 18 | 27 | 28 | {% endblock %} 29 | 30 | 31 | {% block content %} 32 | 33 | {% if chickenwings.id %} 34 |

{% trans 'Update chicken wings' %} #{{ chickenwings.id }}

35 | {% else %} 36 |

{% trans 'Create chicken wings' %}

37 | {% endif %} 38 | 39 | 40 |
41 | {% csrf_token %} 42 | 43 | 44 | {% if form.errors and form.flavour.errors %} 45 |
46 | 47 |
48 | {{ form.flavour }} 49 | {% for error in form.flavour.errors %} 50 | {{ error }} 51 | {% endfor %} 52 |
53 |
54 | {% else %} 55 |
56 | 57 |
58 | {{ form.flavour }} 59 |
60 |
61 | {% endif %} 62 | 63 |
64 |
65 | 66 |
67 |
68 | 69 |
70 | 71 | 72 | {% endblock %} 73 | 74 | 75 | {% block scripts_end %} 76 | 77 | 78 | 79 | 86 | 87 | {% endblock %} -------------------------------------------------------------------------------- /test_projects/django14/forthewing/templates/forthewing/chickenwings_list.html: -------------------------------------------------------------------------------- 1 | {% extends "forthewing/base.html" %} 2 | {% load url from future %} 3 | {% load i18n static %} 4 | {% load chickenwings_tags %} 5 | 6 | 7 | {% block page_title %}{% trans 'Chicken wings' %}{% endblock %} 8 | 9 | 10 | {% block breadcrumbs %} 11 | 12 | 16 | 17 | {% endblock %} 18 | 19 | 20 | {% block content %} 21 | 22 |

{% trans 'Chicken wings orders' %}

23 | 24 |

{% trans 'This app demonstrates a widget that renders select optgroup tags and a field that allows blank choices.' %}

25 | 26 | {% if chickenwings %} 27 | 32 | {% else %} 33 |

{% trans 'No chicken wings found' %}

34 | {% endif %} 35 | 36 | {% if is_paginated %} 37 |
38 |
    39 | {% if page_obj.has_previous %} 40 |
  • «
  • 41 | {% else %} 42 |
  • «
  • 43 | {% endif %} 44 | {% for p in page_obj.paginator.page_range %} 45 | {% ifequal p page_obj.number %} 46 |
  • {{ p }}
  • 47 | {% else %} 48 |
  • {{ p }}
  • 49 | {% endifequal %} 50 | {% empty %} 51 | Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }} 52 | {% endfor %} 53 | {% if page_obj.has_next %} 54 |
  • »
  • 55 | {% else %} 56 |
  • »
  • 57 | {% endif %} 58 |
59 |
60 | {% else %} 61 | 62 | 63 | 64 | {% endif %} 65 | 66 | 67 | {% trans 'Create an order for chicken wings' %} 68 | 69 | {% endblock %} 70 | -------------------------------------------------------------------------------- /test_projects/django14/forthewing/templates/forthewing/chickenwings_updated.html: -------------------------------------------------------------------------------- 1 | {% extends "forthewing/base.html" %} 2 | {% load url from future %} 3 | {% load i18n static %} 4 | 5 | 6 | {% block page_title %}{% trans 'Chicken wings order updated' %}{% endblock %} 7 | 8 | 9 | {% block breadcrumbs %} 10 | 11 | 16 | 17 | {% endblock %} 18 | 19 | 20 | {% block content %} 21 | 22 |

{% trans 'Chicken wings order updated' %}

23 | 24 | 25 |

{% trans 'Your order was updated' %}

26 | 27 | {% trans 'View orders' %} 28 | 29 | {% endblock %} -------------------------------------------------------------------------------- /test_projects/django14/forthewing/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_projects/django14/forthewing/templatetags/__init__.py -------------------------------------------------------------------------------- /test_projects/django14/forthewing/templatetags/chickenwings_tags.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django import template 5 | 6 | from forthewing.models import show_flavour 7 | 8 | 9 | register = template.Library() 10 | 11 | 12 | def decode_order(order): 13 | """ 14 | Decode chicken wings order flavour list 15 | """ 16 | decoded = [show_flavour(f) for f in order] 17 | decoded.sort() 18 | return ', '.join(decoded) 19 | 20 | register.filter('decode_order', decode_order) 21 | 22 | 23 | def decode_flavour(flavour): 24 | """ 25 | Decode a single chicken wings flavour 26 | """ 27 | return show_flavour(flavour) 28 | 29 | register.filter('decode_flavour', decode_flavour) 30 | -------------------------------------------------------------------------------- /test_projects/django14/forthewing/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.core.urlresolvers import reverse 5 | from django.test import SimpleTestCase, TestCase 6 | from django.utils.datastructures import MultiValueDict 7 | from django.utils.http import urlencode 8 | 9 | from .models import ChickenWings, show_flavour 10 | 11 | 12 | class ChickenWingsListViewTestCase(TestCase): 13 | 14 | def test_no_chickenwings(self): 15 | p = ChickenWings.objects.all() 16 | self.assertEqual(len(p), 0, 'Test requires no wings') 17 | response = self.client.get(reverse('ftw:list')) 18 | self.assertEqual(response.status_code, 200) 19 | self.assertTrue( 20 | 'No chicken wings found' in response.content.decode('utf-8')) 21 | 22 | def test_many_chickenwings(self): 23 | NUM_WINGS = 30 24 | wings = [] 25 | for n in range(NUM_WINGS): 26 | p = ChickenWings.objects.create(flavour=[ChickenWings.HONEY_BBQ]) 27 | wings.append(p) 28 | 29 | self.assertEqual(len(wings), NUM_WINGS, 'Test requires chicken wings') 30 | response = self.client.get(reverse('ftw:list')) 31 | self.assertEqual(response.status_code, 200) 32 | self.assertTrue( 33 | (ChickenWings.HONEY_BBQ) in response.content.decode('utf-8')) 34 | 35 | 36 | class ChickenWingsCreateViewTestCase(TestCase): 37 | 38 | def test_view(self): 39 | response = self.client.get(reverse('ftw:create')) 40 | self.assertEqual(response.status_code, 200) 41 | 42 | def test_creation_single(self): 43 | data = { 44 | 'flavour': [ChickenWings.JERK] 45 | } 46 | response = self.client.post( 47 | reverse('ftw:create'), 48 | urlencode(MultiValueDict(data), doseq=True), 49 | content_type='application/x-www-form-urlencoded' 50 | ) 51 | self.assertRedirects( 52 | response, 53 | 'http://testserver' + reverse('ftw:created')) 54 | p = ChickenWings.objects.all()[0] 55 | self.assertIn(ChickenWings.JERK, p.flavour) 56 | 57 | def test_creation_two_choices(self): 58 | data = { 59 | 'flavour': [ChickenWings.SUICIDE, ChickenWings.BOURBON] 60 | } 61 | response = self.client.post( 62 | reverse('ftw:create'), 63 | urlencode(MultiValueDict(data), doseq=True), 64 | content_type='application/x-www-form-urlencoded' 65 | ) 66 | self.assertRedirects( 67 | response, 68 | 'http://testserver' + reverse('ftw:created')) 69 | p = ChickenWings.objects.all()[0] 70 | self.assertIn(ChickenWings.SUICIDE, p.flavour) 71 | self.assertIn(ChickenWings.BOURBON, p.flavour) 72 | 73 | def test_creation_too_many_choices(self): 74 | data = { 75 | 'flavour': [ 76 | ChickenWings.CAJUN, ChickenWings.BOURBON, ChickenWings.MILD] 77 | } 78 | response = self.client.post( 79 | reverse('ftw:create'), 80 | urlencode(MultiValueDict(data), doseq=True), 81 | content_type='application/x-www-form-urlencoded') 82 | self.assertEqual(response.status_code, 200) 83 | self.assertIn('flavour', response.context['form'].errors) 84 | self.assertEqual(len(response.context['form'].errors), 1) 85 | 86 | 87 | class ChickenWingsDetailViewTestCase(TestCase): 88 | 89 | def setUp(self): 90 | self.chickenwings = ChickenWings(flavour=[ChickenWings.HOT]) 91 | self.chickenwings.save() 92 | 93 | def test_view(self): 94 | response = self.client.get( 95 | reverse('ftw:detail', args=[self.chickenwings.id])) 96 | self.assertEqual(response.status_code, 200) 97 | self.assertEqual(response.context['object'], self.chickenwings) 98 | 99 | 100 | class ChickenWingsUpdateViewTestCase(TestCase): 101 | 102 | def setUp(self): 103 | self.chickenwings = ChickenWings(flavour=[ChickenWings.MEDIUM, 104 | ChickenWings.THAI]) 105 | self.chickenwings.save() 106 | 107 | def test_change_flavour(self): 108 | data = { 109 | 'flavour': [ChickenWings.MEDIUM, ChickenWings.BACON] 110 | } 111 | response = self.client.post( 112 | reverse('ftw:update', args=[self.chickenwings.id]), 113 | urlencode(MultiValueDict(data), doseq=True), 114 | content_type='application/x-www-form-urlencoded') 115 | self.assertRedirects( 116 | response, 117 | 'http://testserver' + reverse('ftw:updated')) 118 | p = ChickenWings.objects.all()[0] 119 | self.assertTrue(ChickenWings.MEDIUM in p.flavour) 120 | self.assertFalse(ChickenWings.THAI in p.flavour) 121 | self.assertTrue(ChickenWings.BACON in p.flavour) 122 | 123 | 124 | class ChickenWingsDeleteViewTestCase(TestCase): 125 | 126 | def setUp(self): 127 | self.chickenwings = ChickenWings(flavour=[ChickenWings.HONEY_GARLIC]) 128 | self.chickenwings.save() 129 | 130 | def test_delete_chickenwings(self): 131 | response = self.client.post( 132 | reverse('ftw:delete', args=[self.chickenwings.id])) 133 | self.assertRedirects( 134 | response, 135 | 'http://testserver' + reverse('ftw:deleted')) 136 | pl = ChickenWings.objects.all() 137 | self.assertEqual(len(pl), 0) 138 | 139 | 140 | class ChickenWingsModelTestCase(SimpleTestCase): 141 | 142 | def test_show_flavour(self): 143 | for k, v in ChickenWings.FLAVOUR_CHOICES: 144 | if isinstance(v, (list, tuple)): 145 | for ko, vo in v: 146 | flavour_name = show_flavour(ko) 147 | self.assertEqual(flavour_name, vo) 148 | else: 149 | flavour_name = show_flavour(k) 150 | self.assertEqual(flavour_name, v) 151 | -------------------------------------------------------------------------------- /test_projects/django14/forthewing/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, url 2 | from django.views.generic import TemplateView 3 | 4 | from .views import ( 5 | ChickenWingsCreateView, ChickenWingsDeleteView, ChickenWingsDetailView, 6 | ChickenWingsListView, ChickenWingsUpdateView 7 | ) 8 | 9 | urlpatterns = patterns('', # NOQA 10 | 11 | url(r'^$', ChickenWingsListView.as_view(), name='list'), 12 | url(r'^create/$', ChickenWingsCreateView.as_view(), name='create'), 13 | url(r'^created/$', TemplateView.as_view( 14 | template_name='forthewing/chickenwings_created.html'), name='created'), 15 | url(r'^detail/(?P[0-9]*)$', 16 | ChickenWingsDetailView.as_view(), name='detail'), 17 | url(r'^update/(?P[0-9]*)$', 18 | ChickenWingsUpdateView.as_view(), name='update'), 19 | url(r'^updated/$', TemplateView.as_view( 20 | template_name='forthewing/chickenwings_updated.html'), name='updated'), 21 | url(r'^delete/(?P[0-9]*)$', 22 | ChickenWingsDeleteView.as_view(), name='delete'), 23 | url(r'^deleted/$', TemplateView.as_view( 24 | template_name='forthewing/chickenwings_deleted.html'), name='deleted'), 25 | 26 | ) 27 | -------------------------------------------------------------------------------- /test_projects/django14/forthewing/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.core.urlresolvers import reverse_lazy 5 | from django.views.generic import ( 6 | CreateView, DetailView, DeleteView, ListView, UpdateView) 7 | from django.utils.encoding import force_text 8 | 9 | from .models import ChickenWings 10 | 11 | 12 | class ChickenWingsListView(ListView): 13 | 14 | queryset = ChickenWings.objects.order_by('-id') 15 | context_object_name = 'chickenwings' 16 | paginate_by = 10 17 | 18 | 19 | class ChickenWingsCreateView(CreateView): 20 | 21 | model = ChickenWings 22 | fields = ['flavour'] 23 | success_url = reverse_lazy('ftw:created') 24 | context_object_name = 'wings' 25 | 26 | 27 | class ChickenWingsDetailView(DetailView): 28 | 29 | model = ChickenWings 30 | 31 | 32 | class ChickenWingsUpdateView(UpdateView): 33 | 34 | model = ChickenWings 35 | fields = ['flavour'] 36 | success_url = reverse_lazy('ftw:updated') 37 | 38 | 39 | class ChickenWingsDeleteView(DeleteView): 40 | 41 | model = ChickenWings 42 | success_url = reverse_lazy('ftw:deleted') 43 | 44 | def get_success_url(self): 45 | return force_text(self.success_url) 46 | -------------------------------------------------------------------------------- /test_projects/django14/global_assets/static/css/app.css: -------------------------------------------------------------------------------- 1 | .dropdown-menu > li > form > a { 2 | display: block; 3 | padding: 3px 20px; 4 | clear: both; 5 | font-weight: 400; 6 | color: #333; 7 | white-space: nowrap; 8 | } -------------------------------------------------------------------------------- /test_projects/django14/global_assets/static/css/style-multiselect.css: -------------------------------------------------------------------------------- 1 | .ms-container { 2 | width: 650px; 3 | background-position: 58.5%; 4 | } 5 | .ms-container .ms-list { 6 | margin-left: 0; 7 | } 8 | .ms-container .ms-selectable, .ms-container .ms-selection { 9 | width: 35%; 10 | } -------------------------------------------------------------------------------- /test_projects/django14/global_assets/static/js/script-multiselect.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | 3 | $('#id_toppings').multiSelect(); 4 | 5 | }); -------------------------------------------------------------------------------- /test_projects/django14/global_assets/static/multiselect-0.9.10/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /test_projects/django14/global_assets/static/multiselect-0.9.10/LICENSE.txt: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /test_projects/django14/global_assets/static/multiselect-0.9.10/README.markdown: -------------------------------------------------------------------------------- 1 | # jquery.multi-select.js 2 | 3 | Usage and Demos [http://loudev.com](http://loudev.com "jquery.multi-select.js") 4 | -------------------------------------------------------------------------------- /test_projects/django14/global_assets/static/multiselect-0.9.10/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multiselect", 3 | "version": "0.9.10", 4 | "description" : "A user-friendlier drop-in replacement for the standard select with multiple attribute activated.", 5 | "license" : "WTFPL", 6 | "main": [ 7 | "./css/multi-select.css", 8 | "./img/switch.png", 9 | "./js/jquery.multi-select.js" 10 | ], 11 | "dependencies" : { 12 | "jquery" : ">= 1.7.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test_projects/django14/global_assets/static/multiselect-0.9.10/css/multi-select.css: -------------------------------------------------------------------------------- 1 | .ms-container{ 2 | background: transparent url('../img/switch.png') no-repeat 50% 50%; 3 | width: 370px; 4 | } 5 | 6 | .ms-container:after{ 7 | content: "."; 8 | display: block; 9 | height: 0; 10 | line-height: 0; 11 | font-size: 0; 12 | clear: both; 13 | min-height: 0; 14 | visibility: hidden; 15 | } 16 | 17 | .ms-container .ms-selectable, .ms-container .ms-selection{ 18 | background: #fff; 19 | color: #555555; 20 | float: left; 21 | width: 45%; 22 | } 23 | .ms-container .ms-selection{ 24 | float: right; 25 | } 26 | 27 | .ms-container .ms-list{ 28 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 29 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 30 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 31 | -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; 32 | -moz-transition: border linear 0.2s, box-shadow linear 0.2s; 33 | -ms-transition: border linear 0.2s, box-shadow linear 0.2s; 34 | -o-transition: border linear 0.2s, box-shadow linear 0.2s; 35 | transition: border linear 0.2s, box-shadow linear 0.2s; 36 | border: 1px solid #ccc; 37 | -webkit-border-radius: 3px; 38 | -moz-border-radius: 3px; 39 | border-radius: 3px; 40 | position: relative; 41 | height: 200px; 42 | padding: 0; 43 | overflow-y: auto; 44 | } 45 | 46 | .ms-container .ms-list.ms-focus{ 47 | border-color: rgba(82, 168, 236, 0.8); 48 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 49 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 50 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 51 | outline: 0; 52 | outline: thin dotted \9; 53 | } 54 | 55 | .ms-container ul{ 56 | margin: 0; 57 | list-style-type: none; 58 | padding: 0; 59 | } 60 | 61 | .ms-container .ms-optgroup-container{ 62 | width: 100%; 63 | } 64 | 65 | .ms-container .ms-optgroup-label{ 66 | margin: 0; 67 | padding: 5px 0px 0px 5px; 68 | cursor: pointer; 69 | color: #999; 70 | } 71 | 72 | .ms-container .ms-selectable li.ms-elem-selectable, 73 | .ms-container .ms-selection li.ms-elem-selection{ 74 | border-bottom: 1px #eee solid; 75 | padding: 2px 10px; 76 | color: #555; 77 | font-size: 14px; 78 | } 79 | 80 | .ms-container .ms-selectable li.ms-hover, 81 | .ms-container .ms-selection li.ms-hover{ 82 | cursor: pointer; 83 | color: #fff; 84 | text-decoration: none; 85 | background-color: #08c; 86 | } 87 | 88 | .ms-container .ms-selectable li.disabled, 89 | .ms-container .ms-selection li.disabled{ 90 | background-color: #eee; 91 | color: #aaa; 92 | cursor: text; 93 | } -------------------------------------------------------------------------------- /test_projects/django14/global_assets/static/multiselect-0.9.10/img/switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_projects/django14/global_assets/static/multiselect-0.9.10/img/switch.png -------------------------------------------------------------------------------- /test_projects/django14/global_assets/static/multiselect-0.9.10/multi-select.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multi-select", 3 | "title": "multiselect", 4 | "description": "This is a user-friendlier drop-in replacement for the standard 70 | 71 | {{ lang.1 }} 72 | 73 | 74 | {% endfor %} 75 | 76 | 77 | 84 | 85 | 86 | 87 | 88 | 89 | 90 |
91 | 92 | 93 | {% block messaging %} 94 | {% if messages %} 95 | 96 |
97 |
98 | {% for message in messages %} 99 | 100 | {% if message.tags %} 101 |
102 | {% if 'safe' in message.tags %}{{ message|safe }}{% else %}{{ message }}{% endif %} 103 |
104 | {% else %} 105 |
106 | {{ message }} 107 |
108 | {% endif %} 109 | 110 | {% endfor %} 111 |
112 | {% endif %} 113 | {% block messaging_extra %}{% endblock %} 114 | {% endblock %} 115 | 116 | {% block breadcrumbs %} 117 | 120 | {% endblock %} 121 | 122 | {% block body %}{% endblock %} 123 | 124 | 125 |
126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | {% block scripts_end %}{% endblock %} 138 | 139 | 140 | 141 | 142 | {% endblock %} -------------------------------------------------------------------------------- /test_projects/django14/global_assets/templates/app/home.html: -------------------------------------------------------------------------------- 1 | {% extends "app/base.html" %} 2 | {% load url from future %} 3 | {% load i18n static %} 4 | 5 | 6 | {% block page_title %}{% trans 'Home' %}{% endblock %} 7 | 8 | 9 | {% block body %} 10 | 11 |
12 |
13 |

{% trans 'SelectMultiple Demos' %}

14 | 15 | 19 |
20 |
21 | 22 | {% endblock %} -------------------------------------------------------------------------------- /test_projects/django14/global_assets/templates/theme/html5.html: -------------------------------------------------------------------------------- 1 | 2 | {% load url from future %}{% block html_tag_replacement %} 3 | 4 | {% endblock %} 5 | 6 | {% block base_head %}{% endblock %} 7 | 8 | {% block body_tag_replacement %} 9 | 10 | {% endblock %} 11 | {% block base_body %}{% endblock %} 12 | 13 | -------------------------------------------------------------------------------- /test_projects/django14/global_assets/templates/theme/site_base.html: -------------------------------------------------------------------------------- 1 | {% extends "theme/html5.html" %} 2 | {% load url from future %} 3 | {% load i18n static %} 4 | 5 | 6 | {% block html_tag_replacement %} 7 | 8 | 9 | {% endblock %} 10 | 11 | 12 | {% block base_head %} 13 | 14 | {% block head %} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% endblock %} 24 | {% block extra_head %}{% endblock %} 25 | {% endblock %} 26 | 27 | 28 | {% block base_body %} 29 | 30 | 57 | 58 | 59 | {% block body %}{% endblock %} 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | {% block scripts_end %}{% endblock %} 72 | 73 | 74 | 75 | 76 | {% endblock %} -------------------------------------------------------------------------------- /test_projects/django14/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django14.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /test_projects/django14/pizzagigi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_projects/django14/pizzagigi/__init__.py -------------------------------------------------------------------------------- /test_projects/django14/pizzagigi/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.contrib import admin 3 | 4 | from .models import Pizza 5 | 6 | 7 | class PizzaAdmin(admin.ModelAdmin): 8 | meu_model = Pizza 9 | list_display = ('get_toppings',) 10 | 11 | class Media: 12 | css = { 13 | 'all': ( 14 | '/static/multiselect-0.9.10/css/multi-select.css', 15 | '/static/css/style-multiselect.css',) 16 | } 17 | js = ( 18 | 'http://code.jquery.com/jquery-1.11.0.min.js', 19 | '/static/multiselect-0.9.10/js/jquery.multi-select.js', 20 | '/static/js/script-multiselect.js', 21 | ) 22 | 23 | 24 | admin.site.register(Pizza, PizzaAdmin) 25 | -------------------------------------------------------------------------------- /test_projects/django14/pizzagigi/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.core.urlresolvers import reverse 5 | from django.db import models 6 | from django.utils.encoding import ( 7 | force_text, python_2_unicode_compatible) 8 | from django.utils.translation import ugettext_lazy as _ 9 | 10 | from select_multiple_field.models import SelectMultipleField 11 | 12 | 13 | @python_2_unicode_compatible 14 | class Pizza(models.Model): 15 | """Pizza demonstrates minimal use-case""" 16 | 17 | ANCHOVIES = 'a' 18 | BLACK_OLIVES = 'b' 19 | CHEDDAR_CHEESE = 'c' 20 | EGG = 'e' 21 | PANCETTA = 'pk' 22 | PEPPERONI = 'p' 23 | PROSCIUTTO_CRUDO = 'P' 24 | MOZZARELLA = 'm' 25 | MUSHROOMS = 'M' 26 | TOMATO = 't' 27 | TOPPING_CHOICES = ( 28 | (ANCHOVIES, _('Anchovies')), 29 | (BLACK_OLIVES, _('Black olives')), 30 | (CHEDDAR_CHEESE, _('Cheddar cheese')), 31 | (EGG, _('Eggs')), 32 | (PANCETTA, _('Pancetta')), 33 | (PEPPERONI, _('Pepperoni')), 34 | (PROSCIUTTO_CRUDO, _('Prosciutto crudo')), 35 | (MOZZARELLA, _('Mozzarella')), 36 | (MUSHROOMS, _('Mushrooms')), 37 | (TOMATO, _('Tomato')), 38 | ) 39 | 40 | toppings = SelectMultipleField( 41 | max_length=10, 42 | choices=TOPPING_CHOICES 43 | ) 44 | 45 | def get_toppings(self): 46 | if self.toppings: 47 | keys_choices = self.toppings 48 | return '%s' % (', '.join(filter(bool, keys_choices))) 49 | get_toppings.short_description = _('Toppings') 50 | 51 | def __str__(self): 52 | return "pk=%s" % force_text(self.pk) 53 | 54 | def get_absolute_url(self): 55 | return reverse('pizza:detail', args=[self.pk]) 56 | 57 | 58 | def show_topping(ingredient): 59 | """ 60 | Decode topping to full name 61 | """ 62 | decoder = dict(Pizza.TOPPING_CHOICES) 63 | return force_text(decoder[ingredient]) 64 | -------------------------------------------------------------------------------- /test_projects/django14/pizzagigi/templates/pizzagigi/base.html: -------------------------------------------------------------------------------- 1 | {% extends "app/base.html" %} 2 | {% load url from future %} 3 | {% load i18n static %} 4 | 5 | 6 | {% block page_title %}{% trans 'Pizza demo' %}{% endblock %} 7 | 8 | 9 | {% block messaging_extra %} 10 | 11 | {% if form.errors %} 12 |
13 |
14 |
15 | {% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %} 16 |
17 |
18 |
19 | {% endif %} 20 | 21 | {% endblock %} 22 | 23 | 24 | {% block breadcrumbs %} 25 | 26 | 30 | 31 | {% endblock %} 32 | 33 | {% block body %} 34 | 35 | 36 | {% block content %}{% endblock %} 37 | 38 | 39 | {% endblock %} 40 | -------------------------------------------------------------------------------- /test_projects/django14/pizzagigi/templates/pizzagigi/pizza_confirm_delete.html: -------------------------------------------------------------------------------- 1 | {% extends "pizzagigi/base.html" %} 2 | {% load url from future %} 3 | {% load i18n static %} 4 | 5 | 6 | {% block page_title %}{% trans 'Confirm delete' %}{% endblock %} 7 | 8 | 9 | {% block breadcrumbs %} 10 | 11 | 17 | 18 | {% endblock %} 19 | 20 | 21 | {% block content %} 22 | 23 |

{% trans 'Delete pizza' %} #{{ pizza.id }}

24 | 25 |

{% trans 'Your pizza will be deleted' %}

26 | 27 |
28 | {% csrf_token %} 29 | 30 |
31 | 32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /test_projects/django14/pizzagigi/templates/pizzagigi/pizza_created.html: -------------------------------------------------------------------------------- 1 | {% extends "pizzagigi/base.html" %} 2 | {% load url from future %} 3 | {% load i18n static %} 4 | 5 | 6 | {% block page_title %}{% trans 'Pizza created' %}{% endblock %} 7 | 8 | 9 | {% block breadcrumbs %} 10 | 11 | 16 | 17 | {% endblock %} 18 | 19 | 20 | {% block content %} 21 | 22 |

{% trans 'Pizza created' %}

23 | 24 | 25 |

{% trans 'Your pizza was created' %}

26 | 27 | {% trans 'View pizzas' %} 28 | 29 | {% endblock %} -------------------------------------------------------------------------------- /test_projects/django14/pizzagigi/templates/pizzagigi/pizza_deleted.html: -------------------------------------------------------------------------------- 1 | {% extends "pizzagigi/base.html" %} 2 | {% load url from future %} 3 | {% load i18n static %} 4 | 5 | 6 | {% block page_title %}{% trans 'Pizza deleted' %}{% endblock %} 7 | 8 | 9 | {% block breadcrumbs %} 10 | 11 | 16 | 17 | {% endblock %} 18 | 19 | 20 | {% block content %} 21 | 22 |

{% trans 'Pizza deleted' %}

23 | 24 | 25 |

{% trans 'Your pizza was deleted' %}

26 | 27 | {% trans 'View pizzas' %} 28 | 29 | {% endblock %} -------------------------------------------------------------------------------- /test_projects/django14/pizzagigi/templates/pizzagigi/pizza_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "pizzagigi/base.html" %} 2 | {% load url from future %} 3 | {% load i18n static %} 4 | {% load pizza_tags %} 5 | 6 | 7 | {% block page_title %}{% trans 'Pizza' %} #{{ pizza.id }}{% endblock %} 8 | 9 | 10 | {% block breadcrumbs %} 11 | 12 | 17 | 18 | {% endblock %} 19 | 20 | 21 | {% block content %} 22 | 23 |

{% trans 'Pizza' %} #{{ pizza.id }}

24 | 25 |

{% trans 'The pizza contains the following yummy ingredients' %}

26 | 27 |
    28 | {% for topping in pizza.toppings %} 29 |
  • {{ topping|decode_topping }}
  • 30 | {% empty %} 31 |
  • {% trans 'No toppings' %} 32 | {% endfor %} 33 |
34 | 35 | 40 | 41 | 42 | {% endblock %} -------------------------------------------------------------------------------- /test_projects/django14/pizzagigi/templates/pizzagigi/pizza_form.html: -------------------------------------------------------------------------------- 1 | {% extends "pizzagigi/base.html" %} 2 | {% load url from future %} 3 | {% load i18n static %} 4 | 5 | 6 | {% block page_title %}{% trans 'Pizza order' %}{% endblock %} 7 | 8 | 9 | {% block extra_head %} 10 | 11 | 12 | 13 | {% endblock %} 14 | 15 | 16 | {% block breadcrumbs %} 17 | 18 | 27 | 28 | {% endblock %} 29 | 30 | 31 | {% block content %} 32 | 33 | {% if pizza.id %} 34 |

{% trans 'Update pizza' %} #{{ pizza.id }}

35 | {% else %} 36 |

{% trans 'Create a pizza' %}

37 | {% endif %} 38 | 39 | 40 |
41 | {% csrf_token %} 42 | 43 | 44 | {% if form.errors and form.toppings.errors %} 45 |
46 | 47 |
48 | {{ form.toppings }} 49 | {% for error in form.toppings.errors %} 50 | {{ error }} 51 | {% endfor %} 52 |
53 |
54 | {% else %} 55 |
56 | 57 |
58 | {{ form.toppings }} 59 |
60 |
61 | {% endif %} 62 | 63 |
64 |
65 | 66 |
67 |
68 | 69 |
70 | 71 | 72 | {% endblock %} 73 | 74 | 75 | {% block scripts_end %} 76 | 77 | 78 | 79 | 84 | 85 | < --> 86 | 87 | {% endblock %} -------------------------------------------------------------------------------- /test_projects/django14/pizzagigi/templates/pizzagigi/pizza_list.html: -------------------------------------------------------------------------------- 1 | {% extends "pizzagigi/base.html" %} 2 | {% load url from future %} 3 | {% load i18n static %} 4 | {% load pizza_tags %} 5 | 6 | 7 | {% block page_title %}{% trans 'Pizzas' %}{% endblock %} 8 | 9 | 10 | {% block content %} 11 | 12 |

{% trans 'Pizzas' %}

13 | 14 | {% if pizzas %} 15 | 20 | {% else %} 21 |

{% trans 'No pizzas found' %}

22 | {% endif %} 23 | 24 | {% if is_paginated %} 25 |
26 |
    27 | {% if page_obj.has_previous %} 28 |
  • «
  • 29 | {% else %} 30 |
  • «
  • 31 | {% endif %} 32 | {% for p in page_obj.paginator.page_range %} 33 | {% ifequal p page_obj.number %} 34 |
  • {{ p }}
  • 35 | {% else %} 36 |
  • {{ p }}
  • 37 | {% endifequal %} 38 | {% empty %} 39 | Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }} 40 | {% endfor %} 41 | {% if page_obj.has_next %} 42 |
  • »
  • 43 | {% else %} 44 |
  • »
  • 45 | {% endif %} 46 |
47 |
48 | {% else %} 49 | 50 | 51 | 52 | {% endif %} 53 | 54 | 55 | {% trans 'Create a pizza' %} 56 | 57 | {% endblock %} 58 | -------------------------------------------------------------------------------- /test_projects/django14/pizzagigi/templates/pizzagigi/pizza_updated.html: -------------------------------------------------------------------------------- 1 | {% extends "pizzagigi/base.html" %} 2 | {% load url from future %} 3 | {% load i18n static %} 4 | 5 | 6 | {% block page_title %}{% trans 'Pizza updated' %}{% endblock %} 7 | 8 | 9 | {% block breadcrumbs %} 10 | 11 | 16 | 17 | {% endblock %} 18 | 19 | 20 | {% block content %} 21 | 22 |

{% trans 'Pizza updated' %}

23 | 24 | 25 |

{% trans 'Your pizza was updated' %}

26 | 27 | {% trans 'View pizzas' %} 28 | 29 | {% endblock %} -------------------------------------------------------------------------------- /test_projects/django14/pizzagigi/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_projects/django14/pizzagigi/templatetags/__init__.py -------------------------------------------------------------------------------- /test_projects/django14/pizzagigi/templatetags/pizza_tags.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django import template 5 | 6 | from pizzagigi.models import show_topping 7 | 8 | 9 | register = template.Library() 10 | 11 | 12 | def decode_pie(ingredients): 13 | """ 14 | Decode pizza pie toppings 15 | """ 16 | decoded = [show_topping(t) for t in ingredients] 17 | decoded.sort() 18 | return ', '.join(decoded) 19 | 20 | register.filter('decode_pie', decode_pie) 21 | 22 | 23 | def decode_topping(ingredient): 24 | """ 25 | Decode a single pizza pie topping 26 | """ 27 | return show_topping(ingredient) 28 | 29 | register.filter('decode_topping', decode_topping) 30 | -------------------------------------------------------------------------------- /test_projects/django14/pizzagigi/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | import json 5 | 6 | from django.core import serializers 7 | from django.core.urlresolvers import reverse 8 | from django.test import SimpleTestCase, TestCase 9 | from django.utils.datastructures import MultiValueDict 10 | from django.utils.http import urlencode 11 | 12 | from .models import Pizza, show_topping 13 | 14 | 15 | class PizzaListViewTestCase(TestCase): 16 | 17 | def test_no_pizzas(self): 18 | p = Pizza.objects.all() 19 | self.assertEqual(len(p), 0, 'Test requires no pizzas') 20 | response = self.client.get(reverse('pizza:list')) 21 | self.assertEqual(response.status_code, 200) 22 | self.assertTrue('No pizzas found' in response.content.decode('utf-8')) 23 | 24 | def test_many_pizzas(self): 25 | NUM_PIZZAS = 30 26 | pizzas = [] 27 | for n in range(NUM_PIZZAS): 28 | p = Pizza.objects.create(toppings=[Pizza.PEPPERONI]) 29 | pizzas.append(p) 30 | 31 | self.assertEqual(len(pizzas), NUM_PIZZAS, 'Test requires pizzas') 32 | response = self.client.get(reverse('pizza:list')) 33 | self.assertEqual(response.status_code, 200) 34 | self.assertTrue( 35 | show_topping(Pizza.PEPPERONI) in response.content.decode('utf-8')) 36 | 37 | 38 | class PizzaCreateViewTestCase(TestCase): 39 | 40 | def test_view(self): 41 | response = self.client.get(reverse('pizza:create')) 42 | self.assertEqual(response.status_code, 200) 43 | 44 | def test_creation_single(self): 45 | data = { 46 | 'toppings': [Pizza.BLACK_OLIVES] 47 | } 48 | response = self.client.post( 49 | reverse('pizza:create'), 50 | urlencode(MultiValueDict(data), doseq=True), 51 | content_type='application/x-www-form-urlencoded' 52 | ) 53 | 54 | self.assertEqual(response.status_code, 302) 55 | self.assertRedirects( 56 | response, 57 | 'http://testserver' + reverse('pizza:created')) 58 | p = Pizza.objects.all()[0] 59 | self.assertIn(Pizza.BLACK_OLIVES, p.toppings) 60 | 61 | def test_creation_multiple(self): 62 | data = { 63 | 'toppings': [Pizza.MOZZARELLA, Pizza.PANCETTA] 64 | } 65 | response = self.client.post( 66 | reverse('pizza:create'), 67 | urlencode(MultiValueDict(data), doseq=True), 68 | content_type='application/x-www-form-urlencoded' 69 | ) 70 | self.assertEqual(response.status_code, 302) 71 | self.assertRedirects( 72 | response, 73 | 'http://testserver' + reverse('pizza:created')) 74 | p = Pizza.objects.all()[0] 75 | self.assertIn(Pizza.MOZZARELLA, p.toppings) 76 | self.assertIn(Pizza.PANCETTA, p.toppings) 77 | 78 | 79 | class PizzaDetailViewTestCase(TestCase): 80 | 81 | def setUp(self): 82 | self.pizza = Pizza(toppings=[Pizza.EGG]) 83 | self.pizza.save() 84 | 85 | def test_view(self): 86 | response = self.client.get( 87 | reverse('pizza:detail', args=[self.pizza.id])) 88 | self.assertEqual(response.status_code, 200) 89 | self.assertEqual(response.context['object'], self.pizza) 90 | 91 | 92 | class PizzaUpdateViewTestCase(TestCase): 93 | 94 | def setUp(self): 95 | self.pizza = Pizza(toppings=[Pizza.MUSHROOMS, Pizza.TOMATO]) 96 | self.pizza.save() 97 | 98 | def test_change_toppings(self): 99 | data = { 100 | 'toppings': [Pizza.CHEDDAR_CHEESE, Pizza.MUSHROOMS] 101 | } 102 | response = self.client.post( 103 | reverse('pizza:update', args=[self.pizza.id]), 104 | urlencode(MultiValueDict(data), doseq=True), 105 | content_type='application/x-www-form-urlencoded') 106 | self.assertEqual(response.status_code, 302) 107 | self.assertRedirects( 108 | response, 109 | 'http://testserver' + reverse('pizza:updated')) 110 | p = Pizza.objects.all()[0] 111 | self.assertTrue(Pizza.CHEDDAR_CHEESE in p.toppings) 112 | self.assertTrue(Pizza.MUSHROOMS in p.toppings) 113 | self.assertFalse(Pizza.TOMATO in p.toppings) 114 | 115 | 116 | class PizzaDeleteViewTestCase(TestCase): 117 | 118 | def setUp(self): 119 | self.pizza = Pizza(toppings=[Pizza.PROSCIUTTO_CRUDO]) 120 | self.pizza.save() 121 | 122 | def test_delete_pizza(self): 123 | response = self.client.post( 124 | reverse('pizza:delete', args=[self.pizza.id])) 125 | self.assertEqual(response.status_code, 302) 126 | self.assertRedirects( 127 | response, 128 | 'http://testserver' + reverse('pizza:deleted')) 129 | pl = Pizza.objects.all() 130 | self.assertEqual(len(pl), 0) 131 | 132 | 133 | class PizzaModelTestCase(SimpleTestCase): 134 | 135 | def test_show_topping(self): 136 | for k, v in Pizza.TOPPING_CHOICES: 137 | topping_name = show_topping(k) 138 | self.assertEqual(topping_name, v) 139 | 140 | 141 | class PizzaCozyTestCase(TestCase): 142 | """Serialzer tests for dumpdata operations""" 143 | 144 | def setUp(self): 145 | self.toppings_1 = [ 146 | Pizza.ANCHOVIES, 147 | Pizza.BLACK_OLIVES, 148 | Pizza.CHEDDAR_CHEESE, 149 | ] 150 | self.pizza_1 = Pizza.objects.create(toppings=self.toppings_1) 151 | self.toppings_2 = [ 152 | Pizza.TOMATO, 153 | Pizza.MOZZARELLA, 154 | ] 155 | self.pizza_2 = Pizza.objects.create(toppings=self.toppings_2) 156 | 157 | def test_dumpdata_dumps_json(self): 158 | """JSON can handle a native list type not only strings""" 159 | q = Pizza.objects.all() 160 | output = serializers.serialize('json', q) 161 | 162 | js = json.loads(output) 163 | 164 | self.assertTrue(isinstance(js, list)) 165 | ingredients = [] 166 | for i, __ in enumerate(js): 167 | ingredients.extend(js[i]['fields']['toppings']) 168 | 169 | for topping in list(self.toppings_1 + self.toppings_2): 170 | self.assertIn(topping, ingredients) 171 | -------------------------------------------------------------------------------- /test_projects/django14/pizzagigi/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, url 2 | from django.views.generic import TemplateView 3 | 4 | from .views import ( 5 | PizzaCreateView, PizzaDeleteView, PizzaDetailView, PizzaListView, 6 | PizzaUpdateView 7 | ) 8 | 9 | urlpatterns = patterns('', # NOQA 10 | 11 | url(r'^$', PizzaListView.as_view(), name='list'), 12 | url(r'^create/$', PizzaCreateView.as_view(), name='create'), 13 | url(r'^created/$', TemplateView.as_view( 14 | template_name='pizzagigi/pizza_created.html'), name='created'), 15 | url(r'^detail/(?P[0-9]*)$', PizzaDetailView.as_view(), name='detail'), 16 | url(r'^update/(?P[0-9]*)$', PizzaUpdateView.as_view(), name='update'), 17 | url(r'^updated/$', TemplateView.as_view( 18 | template_name='pizzagigi/pizza_updated.html'), name='updated'), 19 | url(r'^delete/(?P[0-9]*)$', PizzaDeleteView.as_view(), name='delete'), 20 | url(r'^deleted/$', TemplateView.as_view( 21 | template_name='pizzagigi/pizza_deleted.html'), name='deleted'), 22 | 23 | ) 24 | -------------------------------------------------------------------------------- /test_projects/django14/pizzagigi/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.core.urlresolvers import reverse_lazy 5 | from django.views.generic import ( 6 | CreateView, DetailView, DeleteView, ListView, UpdateView) 7 | from django.utils.encoding import force_text 8 | 9 | from .models import Pizza 10 | 11 | 12 | class PizzaListView(ListView): 13 | 14 | queryset = Pizza.objects.order_by('-id') 15 | context_object_name = 'pizzas' 16 | paginate_by = 10 17 | 18 | 19 | class PizzaCreateView(CreateView): 20 | 21 | model = Pizza 22 | fields = ['toppings'] 23 | success_url = reverse_lazy('pizza:created') 24 | 25 | 26 | class PizzaDetailView(DetailView): 27 | 28 | model = Pizza 29 | 30 | 31 | class PizzaUpdateView(UpdateView): 32 | 33 | model = Pizza 34 | fields = ['toppings'] 35 | success_url = reverse_lazy('pizza:updated') 36 | 37 | 38 | class PizzaDeleteView(DeleteView): 39 | 40 | model = Pizza 41 | success_url = reverse_lazy('pizza:deleted') 42 | 43 | def get_success_url(self): 44 | return force_text(self.success_url) 45 | -------------------------------------------------------------------------------- /test_projects/django14/suthern/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_projects/django14/suthern/__init__.py -------------------------------------------------------------------------------- /test_projects/django14/suthern/migration_helpers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.core.management import call_command 5 | from django.test import TransactionTestCase 6 | 7 | from south.migration import Migrations 8 | 9 | 10 | class SouthMigrationTestCase(TransactionTestCase): 11 | """A Test case for testing South migrations.""" 12 | 13 | # Source: 14 | # https://micknelson.wordpress.com/2013/03/01/testing-django-migrations/ 15 | 16 | # These must be defined by subclasses. 17 | start_migration = None 18 | dest_migration = None 19 | django_application = None 20 | 21 | def setUp(self): 22 | super(SouthMigrationTestCase, self).setUp() 23 | migrations = Migrations(self.django_application) 24 | self.start_orm = migrations[self.start_migration].orm() 25 | self.dest_orm = migrations[self.dest_migration].orm() 26 | 27 | # Ensure the migration history is up-to-date with a fake migration. 28 | # The other option would be to use the south setting for these tests 29 | # so that the migrations are used to setup the test db. 30 | call_command('migrate', self.django_application, fake=True, 31 | verbosity=0) 32 | # Then migrate back to the start migration. 33 | call_command('migrate', self.django_application, self.start_migration, 34 | verbosity=0) 35 | 36 | def tearDown(self): 37 | # Leave the db in the final state so that the test runner doesn't 38 | # error when truncating the database. 39 | call_command('migrate', self.django_application, verbosity=0) 40 | 41 | def migrate_to_dest(self): 42 | call_command('migrate', self.django_application, self.dest_migration, 43 | verbosity=0) 44 | -------------------------------------------------------------------------------- /test_projects/django14/suthern/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'ChickenBalls' 12 | db.create_table('suthern_chickenballs', ( 13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('flavour', self.gf(u'select_multiple_field.models.SelectMultipleField')(max_choices=2, include_blank=False, max_length=5, blank=True)), 15 | )) 16 | db.send_create_signal('suthern', ['ChickenBalls']) 17 | 18 | 19 | def backwards(self, orm): 20 | # Deleting model 'ChickenBalls' 21 | db.delete_table('suthern_chickenballs') 22 | 23 | 24 | models = { 25 | 'suthern.chickenballs': { 26 | 'Meta': {'object_name': 'ChickenBalls'}, 27 | 'flavour': (u'select_multiple_field.models.SelectMultipleField', [], {u'max_choices': 2, u'include_blank': False, 'max_length': '5', 'blank': 'True'}), 28 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) 29 | } 30 | } 31 | 32 | complete_apps = ['suthern'] -------------------------------------------------------------------------------- /test_projects/django14/suthern/migrations/0002_auto__add_field_chickenballs_dips.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'ChickenBalls.dips' 12 | db.add_column('suthern_chickenballs', 'dips', 13 | self.gf(u'select_multiple_field.models.SelectMultipleField')(default=u'', max_choices=3, include_blank=False, max_length=6, blank=True), 14 | keep_default=False) 15 | 16 | 17 | def backwards(self, orm): 18 | # Deleting field 'ChickenBalls.dips' 19 | db.delete_column('suthern_chickenballs', 'dips') 20 | 21 | 22 | models = { 23 | 'suthern.chickenballs': { 24 | 'Meta': {'object_name': 'ChickenBalls'}, 25 | 'dips': (u'select_multiple_field.models.SelectMultipleField', [], {'default': "u''", u'max_choices': 3, u'include_blank': False, 'max_length': '6', 'blank': 'True'}), 26 | 'flavour': (u'select_multiple_field.models.SelectMultipleField', [], {u'max_choices': 2, u'include_blank': False, 'max_length': '5', 'blank': 'True'}), 27 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) 28 | } 29 | } 30 | 31 | complete_apps = ['suthern'] -------------------------------------------------------------------------------- /test_projects/django14/suthern/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_projects/django14/suthern/migrations/__init__.py -------------------------------------------------------------------------------- /test_projects/django14/suthern/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.core.urlresolvers import reverse 5 | from django.db import models 6 | from django.utils.encoding import ( 7 | force_text, python_2_unicode_compatible) 8 | from django.utils.translation import ugettext_lazy as _ 9 | 10 | from select_multiple_field.models import SelectMultipleField 11 | 12 | 13 | @python_2_unicode_compatible 14 | class ChickenBalls(models.Model): 15 | """ChickenBalls is used for South migration testing""" 16 | 17 | SUICIDE = 's' 18 | HOT = 'h' 19 | HOME_STYLE = 'H' 20 | CAJUN = 'c' 21 | JERK = 'j' 22 | GATOR = 'g' 23 | FLAVOUR_CHOICES = ( 24 | (_('Hot & Spicy'), ( 25 | (SUICIDE, _('Suicide hot')), 26 | (HOT, _('Hot hot sauce')), 27 | (CAJUN, _('Cajun sauce')), 28 | (JERK, _('Jerk sauce')))), 29 | (_('Traditional'), ( 30 | (HOME_STYLE, _('Homestyle')), 31 | (GATOR, _('Gator flavour')))), 32 | ) 33 | flavour = SelectMultipleField( 34 | blank=True, 35 | include_blank=False, 36 | max_length=5, 37 | max_choices=2, 38 | choices=FLAVOUR_CHOICES 39 | ) 40 | RANCH = 'r' 41 | HONEY_MUSTARD = 'h' 42 | BBQ = 'b' 43 | DIP_CHOICES = ( 44 | (RANCH, _('Ranch')), 45 | (HONEY_MUSTARD, _('Honey mustard')), 46 | (BBQ, _('BBQ')), 47 | ) 48 | dips = SelectMultipleField( 49 | blank=True, 50 | default='', 51 | include_blank=False, 52 | max_length=6, 53 | max_choices=3, 54 | choices=DIP_CHOICES 55 | ) 56 | 57 | def __str__(self): 58 | return "pk=%s" % force_text(self.pk) 59 | 60 | def get_absolute_url(self): 61 | return reverse('ftw:detail', args=[self.pk]) 62 | -------------------------------------------------------------------------------- /test_projects/django14/suthern/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from .migration_helpers import SouthMigrationTestCase 5 | 6 | from .models import ChickenBalls 7 | 8 | 9 | class MyMigrationTestCase(SouthMigrationTestCase): 10 | 11 | start_migration = '0001_initial' 12 | dest_migration = '0002_auto__add_field_chickenballs_dips' 13 | django_application = 'suthern' 14 | 15 | def test_field_survives_migration(self): 16 | self.migrate_to_dest() 17 | 18 | choice_1 = ChickenBalls.HONEY_MUSTARD 19 | order = ChickenBalls() 20 | order.dips = choice_1 21 | order.save() 22 | 23 | self.assertEqual(order.dips, [choice_1]) 24 | -------------------------------------------------------------------------------- /test_projects/django14/suthern/urls.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_projects/django14/suthern/urls.py -------------------------------------------------------------------------------- /test_projects/django14/suthern/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /test_projects/django18/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_projects/django18/__init__.py -------------------------------------------------------------------------------- /test_projects/django18/django18/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_projects/django18/django18/__init__.py -------------------------------------------------------------------------------- /test_projects/django18/django18/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 5 | MODULE_DIR = os.path.join(os.path.dirname(os.path.dirname(BASE_DIR))) 6 | 7 | sys.path.append(BASE_DIR) 8 | sys.path.append(MODULE_DIR) 9 | 10 | # Quick-start development settings - unsuitable for production 11 | # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ 12 | 13 | # SECURITY WARNING: keep the secret key used in production secret! 14 | SECRET_KEY = 'ItsNotaSecretIfItsOnGithub_GuqnyE3l1gKEyAuMJLkD' 15 | 16 | # SECURITY WARNING: don't run with debug turned on in production! 17 | DEBUG = True 18 | 19 | ALLOWED_HOSTS = [] 20 | 21 | 22 | # Application definition 23 | 24 | INSTALLED_APPS = ( 25 | 'select_multiple_field', 26 | 'pizzagigi', 27 | 'forthewing', 28 | 29 | 'django.contrib.admin', 30 | 'django.contrib.auth', 31 | 'django.contrib.contenttypes', 32 | 'django.contrib.sessions', 33 | 'django.contrib.messages', 34 | 'django.contrib.staticfiles', 35 | ) 36 | 37 | MIDDLEWARE_CLASSES = ( 38 | 'django.contrib.sessions.middleware.SessionMiddleware', 39 | 'django.middleware.common.CommonMiddleware', 40 | 'django.middleware.csrf.CsrfViewMiddleware', 41 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 42 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 43 | 'django.contrib.messages.middleware.MessageMiddleware', 44 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 45 | 'django.middleware.security.SecurityMiddleware', 46 | ) 47 | 48 | ROOT_URLCONF = 'django18.urls' 49 | 50 | TEMPLATE_DIRS = ( 51 | # Put strings here, like "/home/html/django_templates" 52 | # Always use forward slashes, even on Windows. 53 | # Don't forget to use absolute paths, not relative paths. 54 | os.path.join(BASE_DIR, 'global_assets', 'templates'), 55 | ) 56 | 57 | TEMPLATES = [ 58 | { 59 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 60 | 'DIRS': TEMPLATE_DIRS, 61 | 'APP_DIRS': True, 62 | 'OPTIONS': { 63 | 'context_processors': [ 64 | 'django.template.context_processors.debug', 65 | 'django.template.context_processors.request', 66 | 'django.contrib.auth.context_processors.auth', 67 | 'django.contrib.messages.context_processors.messages', 68 | ], 69 | }, 70 | }, 71 | ] 72 | 73 | WSGI_APPLICATION = 'django18.wsgi.application' 74 | 75 | 76 | # Database 77 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases 78 | 79 | DATABASES = { 80 | 'default': { 81 | 'ENGINE': 'django.db.backends.sqlite3', 82 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 83 | } 84 | } 85 | 86 | 87 | # Internationalization 88 | # https://docs.djangoproject.com/en/1.8/topics/i18n/ 89 | 90 | LANGUAGE_CODE = 'en-us' 91 | 92 | ugettext = lambda s: s 93 | LANGUAGES = ( 94 | ('en', ugettext('English')), 95 | ('fr', ugettext('French')), 96 | ) 97 | 98 | TIME_ZONE = 'UTC' 99 | 100 | USE_I18N = True 101 | 102 | USE_L10N = True 103 | 104 | USE_TZ = True 105 | 106 | 107 | # Static files (CSS, JavaScript, Images) 108 | # https://docs.djangoproject.com/en/1.8/howto/static-files/ 109 | 110 | STATIC_URL = '/static/' 111 | -------------------------------------------------------------------------------- /test_projects/django18/django18/urls.py: -------------------------------------------------------------------------------- 1 | """django18 URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.8/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Add an import: from blog import urls as blog_urls 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) 15 | """ 16 | from django.conf.urls import include, url 17 | from django.contrib import admin 18 | from django.views.generic import TemplateView 19 | 20 | urlpatterns = [ 21 | url(r'^$', TemplateView.as_view( 22 | template_name='app/home.html'), name='home'), 23 | url(r'^pizzagigi/', include('pizzagigi.urls', namespace='pizza')), 24 | url(r'^forthewing/', include('forthewing.urls', namespace='ftw')), 25 | 26 | url(r'^admin/', include(admin.site.urls)), 27 | ] 28 | -------------------------------------------------------------------------------- /test_projects/django18/django18/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for django18 project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django18.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /test_projects/django18/forthewing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_projects/django18/forthewing/__init__.py -------------------------------------------------------------------------------- /test_projects/django18/forthewing/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.core.urlresolvers import reverse 5 | from django.db import models 6 | from django.utils.encoding import ( 7 | force_text, python_2_unicode_compatible) 8 | from django.utils.translation import ugettext_lazy as _ 9 | 10 | from select_multiple_field.models import SelectMultipleField 11 | 12 | 13 | @python_2_unicode_compatible 14 | class ChickenWings(models.Model): 15 | """ChickenWings demonstrates optgroup usage and max_choices""" 16 | 17 | SUICIDE = 's' 18 | HOT = 'h' 19 | MEDIUM = 'm' 20 | MILD = 'M' 21 | CAJUN = 'c' 22 | JERK = 'j' 23 | HONEY_GARLIC = 'g' 24 | HONEY_BBQ = 'H' 25 | THAI = 't' 26 | BACON = 'b' 27 | BOURBON = 'B' 28 | FLAVOUR_CHOICES = ( 29 | (_('Hot & Spicy'), ( 30 | (SUICIDE, _('Suicide hot')), 31 | (HOT, _('Hot hot sauce')), 32 | (MEDIUM, _('Medium hot sauce')), 33 | (MILD, _('Mild hot sauce')), 34 | (CAJUN, _('Cajun sauce')), 35 | (JERK, _('Jerk sauce')))), 36 | (_('Sweets'), ( 37 | (HONEY_GARLIC, _('Honey garlic')), 38 | (HONEY_BBQ, _('Honey barbeque')), 39 | (THAI, _('Thai sweet sauce')), 40 | (BACON, _('Messy bacon sauce')), 41 | (BOURBON, _('Bourbon whiskey barbeque')))), 42 | ) 43 | flavour = SelectMultipleField( 44 | blank=True, 45 | include_blank=False, 46 | max_length=5, 47 | max_choices=2, 48 | choices=FLAVOUR_CHOICES 49 | ) 50 | 51 | def __str__(self): 52 | return "pk=%s" % force_text(self.pk) 53 | 54 | def get_absolute_url(self): 55 | return reverse('ftw:detail', args=[self.pk]) 56 | 57 | 58 | def show_flavour(flavour): 59 | """ 60 | Decode flavour to full name 61 | 62 | This supports both plain choices and optgroup choices 63 | """ 64 | decoder = dict() 65 | for c in ChickenWings.FLAVOUR_CHOICES: 66 | if isinstance(c[1], (tuple, list)): 67 | for k, v in c[1]: 68 | decoder[k] = v 69 | else: 70 | decoder[c[0]] = c[1] 71 | 72 | if flavour in decoder: 73 | return force_text(decoder[flavour]) 74 | else: 75 | return force_text('') 76 | -------------------------------------------------------------------------------- /test_projects/django18/forthewing/templates/forthewing/base.html: -------------------------------------------------------------------------------- 1 | {% extends "app/base.html" %} 2 | {% load i18n static %} 3 | 4 | 5 | {% block page_title %}{% trans 'Chicken wings demo' %}{% endblock %} 6 | 7 | 8 | {% block messaging_extra %} 9 | 10 | {% if form.errors %} 11 |
12 |
13 |
14 | {% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %} 15 |
16 |
17 |
18 | {% endif %} 19 | 20 | {% endblock %} 21 | 22 | 23 | {% block breadcrumbs %} 24 | 25 | 29 | 30 | {% endblock %} 31 | 32 | {% block body %} 33 | 34 | 35 | {% block content %}{% endblock %} 36 | 37 | 38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /test_projects/django18/forthewing/templates/forthewing/chickenwings_confirm_delete.html: -------------------------------------------------------------------------------- 1 | {% extends "forthewing/base.html" %} 2 | {% load i18n static %} 3 | 4 | 5 | {% block page_title %}{% trans 'Confirm delete' %}{% endblock %} 6 | 7 | 8 | {% block breadcrumbs %} 9 | 10 | 16 | 17 | {% endblock %} 18 | 19 | 20 | {% block content %} 21 | 22 |

{% trans 'Delete chicken wings order' %} #{{ chickenwings.id }}

23 | 24 |

{% trans 'Your order will be deleted' %}

25 | 26 |
27 | {% csrf_token %} 28 | 29 |
30 | 31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /test_projects/django18/forthewing/templates/forthewing/chickenwings_created.html: -------------------------------------------------------------------------------- 1 | {% extends "forthewing/base.html" %} 2 | {% load i18n static %} 3 | 4 | 5 | {% block page_title %}{% trans 'Chicken wings created' %}{% endblock %} 6 | 7 | 8 | {% block breadcrumbs %} 9 | 10 | 15 | 16 | {% endblock %} 17 | 18 | 19 | {% block content %} 20 | 21 |

{% trans 'Chicken wings created' %}

22 | 23 | 24 |

{% trans 'Your Chicken wings were created' %}

25 | 26 | {% trans 'View chicken wings' %} 27 | 28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /test_projects/django18/forthewing/templates/forthewing/chickenwings_deleted.html: -------------------------------------------------------------------------------- 1 | {% extends "forthewing/base.html" %} 2 | {% load i18n static %} 3 | 4 | 5 | {% block page_title %}{% trans 'Order deleted' %}{% endblock %} 6 | 7 | 8 | {% block breadcrumbs %} 9 | 10 | 15 | 16 | {% endblock %} 17 | 18 | 19 | {% block content %} 20 | 21 |

{% trans 'Order deleted' %}

22 | 23 | 24 |

{% trans 'Your chicken wings order was deleted' %}

25 | 26 | {% trans 'View orders' %} 27 | 28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /test_projects/django18/forthewing/templates/forthewing/chickenwings_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "forthewing/base.html" %} 2 | {% load i18n static %} 3 | {% load chickenwings_tags %} 4 | 5 | 6 | {% block page_title %}{% trans 'Chicken wings order' %} #{{ chickenwings.id }}{% endblock %} 7 | 8 | 9 | {% block breadcrumbs %} 10 | 11 | 16 | 17 | {% endblock %} 18 | 19 | 20 | {% block content %} 21 | 22 |

{% trans 'Chicken wings order' %} #{{ chickenwings.id }}

23 | 24 |

{% trans 'The chicken wings have been flavoured with the following sauces.' %}

25 | 26 |
    27 | {% for flavour in chickenwings.flavour %} 28 |
  • {{ flavour|decode_flavour }}
  • 29 | {% empty %} 30 |
  • {% trans 'No sauce! Whaaa?' %} 31 | {% endfor %} 32 |
33 | 34 | 39 | 40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /test_projects/django18/forthewing/templates/forthewing/chickenwings_form.html: -------------------------------------------------------------------------------- 1 | {% extends "forthewing/base.html" %} 2 | {% load i18n static %} 3 | 4 | 5 | {% block page_title %}{% if chickenwings.id %}{% trans 'Update chicken wings' %} #{{ chickenwings.id }}{% else %}{% trans 'Create chicken wings' %}{% endif %}{% endblock %} 6 | 7 | 8 | {% block extra_head %} 9 | 10 | 11 | 12 | {% endblock %} 13 | 14 | 15 | {% block breadcrumbs %} 16 | 17 | 26 | 27 | {% endblock %} 28 | 29 | 30 | {% block content %} 31 | 32 | {% if chickenwings.id %} 33 |

{% trans 'Update chicken wings' %} #{{ chickenwings.id }}

34 | {% else %} 35 |

{% trans 'Create chicken wings' %}

36 | {% endif %} 37 | 38 | 39 |
40 | {% csrf_token %} 41 | 42 | 43 | {% if form.errors and form.flavour.errors %} 44 |
45 | 46 |
47 | {{ form.flavour }} 48 | {% for error in form.flavour.errors %} 49 | {{ error }} 50 | {% endfor %} 51 |
52 |
53 | {% else %} 54 |
55 | 56 |
57 | {{ form.flavour }} 58 |
59 |
60 | {% endif %} 61 | 62 |
63 |
64 | 65 |
66 |
67 | 68 |
69 | 70 | 71 | {% endblock %} 72 | 73 | 74 | {% block scripts_end %} 75 | 76 | 77 | 78 | 85 | 86 | {% endblock %} 87 | -------------------------------------------------------------------------------- /test_projects/django18/forthewing/templates/forthewing/chickenwings_list.html: -------------------------------------------------------------------------------- 1 | {% extends "forthewing/base.html" %} 2 | {% load i18n static %} 3 | {% load chickenwings_tags %} 4 | 5 | 6 | {% block page_title %}{% trans 'Chicken wings' %}{% endblock %} 7 | 8 | 9 | {% block breadcrumbs %} 10 | 11 | 15 | 16 | {% endblock %} 17 | 18 | 19 | {% block content %} 20 | 21 |

{% trans 'Chicken wings orders' %}

22 | 23 |

{% trans 'This app demonstrates a widget that renders select optgroup tags and a field that allows blank choices.' %}

24 | 25 | {% if chickenwings %} 26 | 31 | {% else %} 32 |

{% trans 'No chicken wings found' %}

33 | {% endif %} 34 | 35 | {% if is_paginated %} 36 |
37 |
    38 | {% if page_obj.has_previous %} 39 |
  • «
  • 40 | {% else %} 41 |
  • «
  • 42 | {% endif %} 43 | {% for p in page_obj.paginator.page_range %} 44 | {% ifequal p page_obj.number %} 45 |
  • {{ p }}
  • 46 | {% else %} 47 |
  • {{ p }}
  • 48 | {% endifequal %} 49 | {% empty %} 50 | Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }} 51 | {% endfor %} 52 | {% if page_obj.has_next %} 53 |
  • »
  • 54 | {% else %} 55 |
  • »
  • 56 | {% endif %} 57 |
58 |
59 | {% else %} 60 | 61 | 62 | 63 | {% endif %} 64 | 65 | 66 | {% trans 'Create an order for chicken wings' %} 67 | 68 | {% endblock %} 69 | -------------------------------------------------------------------------------- /test_projects/django18/forthewing/templates/forthewing/chickenwings_updated.html: -------------------------------------------------------------------------------- 1 | {% extends "forthewing/base.html" %} 2 | {% load i18n static %} 3 | 4 | 5 | {% block page_title %}{% trans 'Chicken wings order updated' %}{% endblock %} 6 | 7 | 8 | {% block breadcrumbs %} 9 | 10 | 15 | 16 | {% endblock %} 17 | 18 | 19 | {% block content %} 20 | 21 |

{% trans 'Chicken wings order updated' %}

22 | 23 | 24 |

{% trans 'Your order was updated' %}

25 | 26 | {% trans 'View orders' %} 27 | 28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /test_projects/django18/forthewing/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_projects/django18/forthewing/templatetags/__init__.py -------------------------------------------------------------------------------- /test_projects/django18/forthewing/templatetags/chickenwings_tags.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django import template 5 | 6 | from forthewing.models import show_flavour 7 | 8 | 9 | register = template.Library() 10 | 11 | 12 | def decode_order(order): 13 | """ 14 | Decode chicken wings order flavour list 15 | """ 16 | decoded = [show_flavour(f) for f in order] 17 | decoded.sort() 18 | return ', '.join(decoded) 19 | 20 | register.filter('decode_order', decode_order) 21 | 22 | 23 | def decode_flavour(flavour): 24 | """ 25 | Decode a single chicken wings flavour 26 | """ 27 | return show_flavour(flavour) 28 | 29 | register.filter('decode_flavour', decode_flavour) 30 | -------------------------------------------------------------------------------- /test_projects/django18/forthewing/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.core.urlresolvers import reverse 5 | from django.test import SimpleTestCase, TestCase 6 | from django.utils.datastructures import MultiValueDict 7 | from django.utils.http import urlencode 8 | 9 | from .models import ChickenWings, show_flavour 10 | 11 | 12 | class ChickenWingsListViewTestCase(TestCase): 13 | 14 | def test_no_chickenwings(self): 15 | p = ChickenWings.objects.all() 16 | self.assertEqual(len(p), 0, 'Test requires no wings') 17 | response = self.client.get(reverse('ftw:list')) 18 | self.assertEqual(response.status_code, 200) 19 | self.assertTrue( 20 | 'No chicken wings found' in response.content.decode('utf-8')) 21 | 22 | def test_many_chickenwings(self): 23 | NUM_WINGS = 30 24 | wings = [] 25 | for n in range(NUM_WINGS): 26 | p = ChickenWings.objects.create(flavour=[ChickenWings.HONEY_BBQ]) 27 | wings.append(p) 28 | 29 | self.assertEqual(len(wings), NUM_WINGS, 'Test requires chicken wings') 30 | response = self.client.get(reverse('ftw:list')) 31 | self.assertEqual(response.status_code, 200) 32 | self.assertTrue( 33 | (ChickenWings.HONEY_BBQ) in response.content.decode('utf-8')) 34 | 35 | 36 | class ChickenWingsCreateViewTestCase(TestCase): 37 | 38 | def test_view(self): 39 | response = self.client.get(reverse('ftw:create')) 40 | self.assertEqual(response.status_code, 200) 41 | 42 | def test_creation_single(self): 43 | data = { 44 | 'flavour': [ChickenWings.JERK] 45 | } 46 | response = self.client.post( 47 | reverse('ftw:create'), 48 | urlencode(MultiValueDict(data), doseq=True), 49 | content_type='application/x-www-form-urlencoded' 50 | ) 51 | self.assertRedirects( 52 | response, 53 | 'http://testserver' + reverse('ftw:created')) 54 | p = ChickenWings.objects.all()[0] 55 | self.assertIn(ChickenWings.JERK, p.flavour) 56 | 57 | def test_creation_two_choices(self): 58 | data = { 59 | 'flavour': [ChickenWings.SUICIDE, ChickenWings.BOURBON] 60 | } 61 | response = self.client.post( 62 | reverse('ftw:create'), 63 | urlencode(MultiValueDict(data), doseq=True), 64 | content_type='application/x-www-form-urlencoded' 65 | ) 66 | self.assertRedirects( 67 | response, 68 | 'http://testserver' + reverse('ftw:created')) 69 | p = ChickenWings.objects.all()[0] 70 | self.assertIn(ChickenWings.SUICIDE, p.flavour) 71 | self.assertIn(ChickenWings.BOURBON, p.flavour) 72 | 73 | def test_creation_too_many_choices(self): 74 | data = { 75 | 'flavour': [ 76 | ChickenWings.CAJUN, ChickenWings.BOURBON, ChickenWings.MILD] 77 | } 78 | response = self.client.post( 79 | reverse('ftw:create'), 80 | urlencode(MultiValueDict(data), doseq=True), 81 | content_type='application/x-www-form-urlencoded') 82 | self.assertEqual(response.status_code, 200) 83 | self.assertIn('flavour', response.context['form'].errors) 84 | self.assertEqual(len(response.context['form'].errors), 1) 85 | 86 | 87 | class ChickenWingsDetailViewTestCase(TestCase): 88 | 89 | def setUp(self): 90 | self.chickenwings = ChickenWings(flavour=[ChickenWings.HOT]) 91 | self.chickenwings.save() 92 | 93 | def test_view(self): 94 | response = self.client.get( 95 | reverse('ftw:detail', args=[self.chickenwings.id])) 96 | self.assertEqual(response.status_code, 200) 97 | self.assertEqual(response.context['object'], self.chickenwings) 98 | 99 | 100 | class ChickenWingsUpdateViewTestCase(TestCase): 101 | 102 | def setUp(self): 103 | self.chickenwings = ChickenWings(flavour=[ChickenWings.MEDIUM, 104 | ChickenWings.THAI]) 105 | self.chickenwings.save() 106 | 107 | def test_change_flavour(self): 108 | data = { 109 | 'flavour': [ChickenWings.MEDIUM, ChickenWings.BACON] 110 | } 111 | response = self.client.post( 112 | reverse('ftw:update', args=[self.chickenwings.id]), 113 | urlencode(MultiValueDict(data), doseq=True), 114 | content_type='application/x-www-form-urlencoded') 115 | self.assertRedirects( 116 | response, 117 | 'http://testserver' + reverse('ftw:updated')) 118 | p = ChickenWings.objects.all()[0] 119 | self.assertTrue(ChickenWings.MEDIUM in p.flavour) 120 | self.assertFalse(ChickenWings.THAI in p.flavour) 121 | self.assertTrue(ChickenWings.BACON in p.flavour) 122 | 123 | 124 | class ChickenWingsDeleteViewTestCase(TestCase): 125 | 126 | def setUp(self): 127 | self.chickenwings = ChickenWings(flavour=[ChickenWings.HONEY_GARLIC]) 128 | self.chickenwings.save() 129 | 130 | def test_delete_chickenwings(self): 131 | response = self.client.post( 132 | reverse('ftw:delete', args=[self.chickenwings.id])) 133 | self.assertRedirects( 134 | response, 135 | 'http://testserver' + reverse('ftw:deleted')) 136 | pl = ChickenWings.objects.all() 137 | self.assertEqual(len(pl), 0) 138 | 139 | 140 | class ChickenWingsModelTestCase(SimpleTestCase): 141 | 142 | def test_show_flavour(self): 143 | for k, v in ChickenWings.FLAVOUR_CHOICES: 144 | if isinstance(v, (list, tuple)): 145 | for ko, vo in v: 146 | flavour_name = show_flavour(ko) 147 | self.assertEqual(flavour_name, vo) 148 | else: 149 | flavour_name = show_flavour(k) 150 | self.assertEqual(flavour_name, v) 151 | -------------------------------------------------------------------------------- /test_projects/django18/forthewing/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, url 2 | from django.views.generic import TemplateView 3 | 4 | from .views import ( 5 | ChickenWingsCreateView, ChickenWingsDeleteView, ChickenWingsDetailView, 6 | ChickenWingsListView, ChickenWingsUpdateView 7 | ) 8 | 9 | urlpatterns = patterns('', # NOQA 10 | 11 | url(r'^$', ChickenWingsListView.as_view(), name='list'), 12 | url(r'^create/$', ChickenWingsCreateView.as_view(), name='create'), 13 | url(r'^created/$', TemplateView.as_view( 14 | template_name='forthewing/chickenwings_created.html'), name='created'), 15 | url(r'^detail/(?P[0-9]*)$', 16 | ChickenWingsDetailView.as_view(), name='detail'), 17 | url(r'^update/(?P[0-9]*)$', 18 | ChickenWingsUpdateView.as_view(), name='update'), 19 | url(r'^updated/$', TemplateView.as_view( 20 | template_name='forthewing/chickenwings_updated.html'), name='updated'), 21 | url(r'^delete/(?P[0-9]*)$', 22 | ChickenWingsDeleteView.as_view(), name='delete'), 23 | url(r'^deleted/$', TemplateView.as_view( 24 | template_name='forthewing/chickenwings_deleted.html'), name='deleted'), 25 | 26 | ) 27 | -------------------------------------------------------------------------------- /test_projects/django18/forthewing/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.core.urlresolvers import reverse_lazy 5 | from django.views.generic import ( 6 | CreateView, DetailView, DeleteView, ListView, UpdateView) 7 | from django.utils.encoding import force_text 8 | 9 | from .models import ChickenWings 10 | 11 | 12 | class ChickenWingsListView(ListView): 13 | 14 | queryset = ChickenWings.objects.order_by('-id') 15 | context_object_name = 'chickenwings' 16 | paginate_by = 10 17 | 18 | 19 | class ChickenWingsCreateView(CreateView): 20 | 21 | model = ChickenWings 22 | fields = ['flavour'] 23 | success_url = reverse_lazy('ftw:created') 24 | context_object_name = 'wings' 25 | 26 | 27 | class ChickenWingsDetailView(DetailView): 28 | 29 | model = ChickenWings 30 | 31 | 32 | class ChickenWingsUpdateView(UpdateView): 33 | 34 | model = ChickenWings 35 | fields = ['flavour'] 36 | success_url = reverse_lazy('ftw:updated') 37 | 38 | 39 | class ChickenWingsDeleteView(DeleteView): 40 | 41 | model = ChickenWings 42 | success_url = reverse_lazy('ftw:deleted') 43 | 44 | def get_success_url(self): 45 | return force_text(self.success_url) 46 | -------------------------------------------------------------------------------- /test_projects/django18/global_assets/static/css/app.css: -------------------------------------------------------------------------------- 1 | .dropdown-menu > li > form > a { 2 | display: block; 3 | padding: 3px 20px; 4 | clear: both; 5 | font-weight: 400; 6 | color: #333; 7 | white-space: nowrap; 8 | } -------------------------------------------------------------------------------- /test_projects/django18/global_assets/static/css/style-multiselect.css: -------------------------------------------------------------------------------- 1 | .ms-container { 2 | width: 650px; 3 | background-position: 58.5%; 4 | } 5 | .ms-container .ms-list { 6 | margin-left: 0; 7 | } 8 | .ms-container .ms-selectable, .ms-container .ms-selection { 9 | width: 35%; 10 | } -------------------------------------------------------------------------------- /test_projects/django18/global_assets/static/js/script-multiselect.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | 3 | $('#id_toppings').multiSelect(); 4 | 5 | }); -------------------------------------------------------------------------------- /test_projects/django18/global_assets/static/multiselect-0.9.10/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /test_projects/django18/global_assets/static/multiselect-0.9.10/LICENSE.txt: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /test_projects/django18/global_assets/static/multiselect-0.9.10/README.markdown: -------------------------------------------------------------------------------- 1 | # jquery.multi-select.js 2 | 3 | Usage and Demos [http://loudev.com](http://loudev.com "jquery.multi-select.js") 4 | -------------------------------------------------------------------------------- /test_projects/django18/global_assets/static/multiselect-0.9.10/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multiselect", 3 | "version": "0.9.10", 4 | "description" : "A user-friendlier drop-in replacement for the standard select with multiple attribute activated.", 5 | "license" : "WTFPL", 6 | "main": [ 7 | "./css/multi-select.css", 8 | "./img/switch.png", 9 | "./js/jquery.multi-select.js" 10 | ], 11 | "dependencies" : { 12 | "jquery" : ">= 1.7.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test_projects/django18/global_assets/static/multiselect-0.9.10/css/multi-select.css: -------------------------------------------------------------------------------- 1 | .ms-container{ 2 | background: transparent url('../img/switch.png') no-repeat 50% 50%; 3 | width: 370px; 4 | } 5 | 6 | .ms-container:after{ 7 | content: "."; 8 | display: block; 9 | height: 0; 10 | line-height: 0; 11 | font-size: 0; 12 | clear: both; 13 | min-height: 0; 14 | visibility: hidden; 15 | } 16 | 17 | .ms-container .ms-selectable, .ms-container .ms-selection{ 18 | background: #fff; 19 | color: #555555; 20 | float: left; 21 | width: 45%; 22 | } 23 | .ms-container .ms-selection{ 24 | float: right; 25 | } 26 | 27 | .ms-container .ms-list{ 28 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 29 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 30 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 31 | -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; 32 | -moz-transition: border linear 0.2s, box-shadow linear 0.2s; 33 | -ms-transition: border linear 0.2s, box-shadow linear 0.2s; 34 | -o-transition: border linear 0.2s, box-shadow linear 0.2s; 35 | transition: border linear 0.2s, box-shadow linear 0.2s; 36 | border: 1px solid #ccc; 37 | -webkit-border-radius: 3px; 38 | -moz-border-radius: 3px; 39 | border-radius: 3px; 40 | position: relative; 41 | height: 200px; 42 | padding: 0; 43 | overflow-y: auto; 44 | } 45 | 46 | .ms-container .ms-list.ms-focus{ 47 | border-color: rgba(82, 168, 236, 0.8); 48 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 49 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 50 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 51 | outline: 0; 52 | outline: thin dotted \9; 53 | } 54 | 55 | .ms-container ul{ 56 | margin: 0; 57 | list-style-type: none; 58 | padding: 0; 59 | } 60 | 61 | .ms-container .ms-optgroup-container{ 62 | width: 100%; 63 | } 64 | 65 | .ms-container .ms-optgroup-label{ 66 | margin: 0; 67 | padding: 5px 0px 0px 5px; 68 | cursor: pointer; 69 | color: #999; 70 | } 71 | 72 | .ms-container .ms-selectable li.ms-elem-selectable, 73 | .ms-container .ms-selection li.ms-elem-selection{ 74 | border-bottom: 1px #eee solid; 75 | padding: 2px 10px; 76 | color: #555; 77 | font-size: 14px; 78 | } 79 | 80 | .ms-container .ms-selectable li.ms-hover, 81 | .ms-container .ms-selection li.ms-hover{ 82 | cursor: pointer; 83 | color: #fff; 84 | text-decoration: none; 85 | background-color: #08c; 86 | } 87 | 88 | .ms-container .ms-selectable li.disabled, 89 | .ms-container .ms-selection li.disabled{ 90 | background-color: #eee; 91 | color: #aaa; 92 | cursor: text; 93 | } -------------------------------------------------------------------------------- /test_projects/django18/global_assets/static/multiselect-0.9.10/img/switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_projects/django18/global_assets/static/multiselect-0.9.10/img/switch.png -------------------------------------------------------------------------------- /test_projects/django18/global_assets/static/multiselect-0.9.10/multi-select.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multi-select", 3 | "title": "multiselect", 4 | "description": "This is a user-friendlier drop-in replacement for the standard 69 | 70 | {{ lang.1 }} 71 | 72 | 73 | {% endfor %} 74 | 75 | 76 | 83 | 84 |
85 | 86 | 87 | 88 | 89 |
90 | 91 | 92 | {% block messaging %} 93 | {% if messages %} 94 | 95 |
96 |
97 | {% for message in messages %} 98 | 99 | {% if message.tags %} 100 |
101 | {% if 'safe' in message.tags %}{{ message|safe }}{% else %}{{ message }}{% endif %} 102 |
103 | {% else %} 104 |
105 | {{ message }} 106 |
107 | {% endif %} 108 | 109 | {% endfor %} 110 |
111 | {% endif %} 112 | {% block messaging_extra %}{% endblock %} 113 | {% endblock %} 114 | 115 | {% block breadcrumbs %} 116 | 119 | {% endblock %} 120 | 121 | {% block body %}{% endblock %} 122 | 123 | 124 |
125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | {% block scripts_end %}{% endblock %} 137 | 138 | 139 | 140 | 141 | {% endblock %} 142 | -------------------------------------------------------------------------------- /test_projects/django18/global_assets/templates/app/home.html: -------------------------------------------------------------------------------- 1 | {% extends "app/base.html" %} 2 | {% load i18n static %} 3 | 4 | 5 | {% block page_title %}{% trans 'Home' %}{% endblock %} 6 | 7 | 8 | {% block body %} 9 | 10 |
11 |
12 |

{% trans 'SelectMultiple Demos' %}

13 | 14 | 18 |
19 |
20 | 21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /test_projects/django18/global_assets/templates/theme/html5.html: -------------------------------------------------------------------------------- 1 | 2 | {% block html_tag_replacement %} 3 | 4 | {% endblock %} 5 | 6 | {% block base_head %}{% endblock %} 7 | 8 | {% block body_tag_replacement %} 9 | 10 | {% endblock %} 11 | {% block base_body %}{% endblock %} 12 | 13 | 14 | -------------------------------------------------------------------------------- /test_projects/django18/global_assets/templates/theme/site_base.html: -------------------------------------------------------------------------------- 1 | {% extends "theme/html5.html" %} 2 | {% load i18n static %} 3 | 4 | 5 | {% block html_tag_replacement %} 6 | 7 | 8 | {% endblock %} 9 | 10 | 11 | {% block base_head %} 12 | 13 | {% block head %} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% endblock %} 23 | {% block extra_head %}{% endblock %} 24 | {% endblock %} 25 | 26 | 27 | {% block base_body %} 28 | 29 | 56 | 57 | 58 | {% block body %}{% endblock %} 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | {% block scripts_end %}{% endblock %} 71 | 72 | 73 | 74 | 75 | {% endblock %} 76 | -------------------------------------------------------------------------------- /test_projects/django18/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django18.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /test_projects/django18/pizzagigi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_projects/django18/pizzagigi/__init__.py -------------------------------------------------------------------------------- /test_projects/django18/pizzagigi/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.contrib import admin 3 | 4 | from .models import Pizza 5 | 6 | 7 | class PizzaAdmin(admin.ModelAdmin): 8 | meu_model = Pizza 9 | list_display = ('get_toppings',) 10 | 11 | class Media: 12 | css = { 13 | 'all': ( 14 | '/static/multiselect-0.9.10/css/multi-select.css', 15 | '/static/css/style-multiselect.css',) 16 | } 17 | js = ( 18 | 'http://code.jquery.com/jquery-1.11.0.min.js', 19 | '/static/multiselect-0.9.10/js/jquery.multi-select.js', 20 | '/static/js/script-multiselect.js', 21 | ) 22 | 23 | 24 | admin.site.register(Pizza, PizzaAdmin) 25 | -------------------------------------------------------------------------------- /test_projects/django18/pizzagigi/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.core.urlresolvers import reverse 5 | from django.db import models 6 | from django.utils.encoding import ( 7 | force_text, python_2_unicode_compatible) 8 | from django.utils.translation import ugettext_lazy as _ 9 | 10 | from select_multiple_field.models import SelectMultipleField 11 | 12 | 13 | @python_2_unicode_compatible 14 | class Pizza(models.Model): 15 | """Pizza demonstrates minimal use-case""" 16 | 17 | ANCHOVIES = 'a' 18 | BLACK_OLIVES = 'b' 19 | CHEDDAR_CHEESE = 'c' 20 | EGG = 'e' 21 | PANCETTA = 'pk' 22 | PEPPERONI = 'p' 23 | PROSCIUTTO_CRUDO = 'P' 24 | MOZZARELLA = 'm' 25 | MUSHROOMS = 'M' 26 | TOMATO = 't' 27 | TOPPING_CHOICES = ( 28 | (ANCHOVIES, _('Anchovies')), 29 | (BLACK_OLIVES, _('Black olives')), 30 | (CHEDDAR_CHEESE, _('Cheddar cheese')), 31 | (EGG, _('Eggs')), 32 | (PANCETTA, _('Pancetta')), 33 | (PEPPERONI, _('Pepperoni')), 34 | (PROSCIUTTO_CRUDO, _('Prosciutto crudo')), 35 | (MOZZARELLA, _('Mozzarella')), 36 | (MUSHROOMS, _('Mushrooms')), 37 | (TOMATO, _('Tomato')), 38 | ) 39 | 40 | toppings = SelectMultipleField( 41 | max_length=10, 42 | choices=TOPPING_CHOICES 43 | ) 44 | 45 | def get_toppings(self): 46 | if self.toppings: 47 | keys_choices = self.toppings 48 | return '%s' % (', '.join(filter(bool, keys_choices))) 49 | get_toppings.short_description = _('Toppings') 50 | 51 | def __str__(self): 52 | return "pk=%s" % force_text(self.pk) 53 | 54 | def get_absolute_url(self): 55 | return reverse('pizza:detail', args=[self.pk]) 56 | 57 | 58 | def show_topping(ingredient): 59 | """ 60 | Decode topping to full name 61 | """ 62 | decoder = dict(Pizza.TOPPING_CHOICES) 63 | return force_text(decoder[ingredient]) 64 | -------------------------------------------------------------------------------- /test_projects/django18/pizzagigi/templates/pizzagigi/base.html: -------------------------------------------------------------------------------- 1 | {% extends "app/base.html" %} 2 | {% load i18n static %} 3 | 4 | 5 | {% block page_title %}{% trans 'Pizza demo' %}{% endblock %} 6 | 7 | 8 | {% block messaging_extra %} 9 | 10 | {% if form.errors %} 11 |
12 |
13 |
14 | {% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %} 15 |
16 |
17 |
18 | {% endif %} 19 | 20 | {% endblock %} 21 | 22 | 23 | {% block breadcrumbs %} 24 | 25 | 29 | 30 | {% endblock %} 31 | 32 | {% block body %} 33 | 34 | 35 | {% block content %}{% endblock %} 36 | 37 | 38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /test_projects/django18/pizzagigi/templates/pizzagigi/pizza_confirm_delete.html: -------------------------------------------------------------------------------- 1 | {% extends "pizzagigi/base.html" %} 2 | {% load i18n static %} 3 | 4 | 5 | {% block page_title %}{% trans 'Confirm delete' %}{% endblock %} 6 | 7 | 8 | {% block breadcrumbs %} 9 | 10 | 16 | 17 | {% endblock %} 18 | 19 | 20 | {% block content %} 21 | 22 |

{% trans 'Delete pizza' %} #{{ pizza.id }}

23 | 24 |

{% trans 'Your pizza will be deleted' %}

25 | 26 |
27 | {% csrf_token %} 28 | 29 |
30 | 31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /test_projects/django18/pizzagigi/templates/pizzagigi/pizza_created.html: -------------------------------------------------------------------------------- 1 | {% extends "pizzagigi/base.html" %} 2 | {% load i18n static %} 3 | 4 | 5 | {% block page_title %}{% trans 'Pizza created' %}{% endblock %} 6 | 7 | 8 | {% block breadcrumbs %} 9 | 10 | 15 | 16 | {% endblock %} 17 | 18 | 19 | {% block content %} 20 | 21 |

{% trans 'Pizza created' %}

22 | 23 | 24 |

{% trans 'Your pizza was created' %}

25 | 26 | {% trans 'View pizzas' %} 27 | 28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /test_projects/django18/pizzagigi/templates/pizzagigi/pizza_deleted.html: -------------------------------------------------------------------------------- 1 | {% extends "pizzagigi/base.html" %} 2 | {% load i18n static %} 3 | 4 | 5 | {% block page_title %}{% trans 'Pizza deleted' %}{% endblock %} 6 | 7 | 8 | {% block breadcrumbs %} 9 | 10 | 15 | 16 | {% endblock %} 17 | 18 | 19 | {% block content %} 20 | 21 |

{% trans 'Pizza deleted' %}

22 | 23 | 24 |

{% trans 'Your pizza was deleted' %}

25 | 26 | {% trans 'View pizzas' %} 27 | 28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /test_projects/django18/pizzagigi/templates/pizzagigi/pizza_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "pizzagigi/base.html" %} 2 | {% load i18n static %} 3 | {% load pizza_tags %} 4 | 5 | 6 | {% block page_title %}{% trans 'Pizza' %} #{{ pizza.id }}{% endblock %} 7 | 8 | 9 | {% block breadcrumbs %} 10 | 11 | 16 | 17 | {% endblock %} 18 | 19 | 20 | {% block content %} 21 | 22 |

{% trans 'Pizza' %} #{{ pizza.id }}

23 | 24 |

{% trans 'The pizza contains the following yummy ingredients' %}

25 | 26 |
    27 | {% for topping in pizza.toppings %} 28 |
  • {{ topping|decode_topping }}
  • 29 | {% empty %} 30 |
  • {% trans 'No toppings' %} 31 | {% endfor %} 32 |
33 | 34 | 39 | 40 | 41 | {% endblock %} 42 | -------------------------------------------------------------------------------- /test_projects/django18/pizzagigi/templates/pizzagigi/pizza_form.html: -------------------------------------------------------------------------------- 1 | {% extends "pizzagigi/base.html" %} 2 | {% load i18n static %} 3 | 4 | 5 | {% block page_title %}{% trans 'Pizza order' %}{% endblock %} 6 | 7 | 8 | {% block extra_head %} 9 | 10 | 11 | 12 | {% endblock %} 13 | 14 | 15 | {% block breadcrumbs %} 16 | 17 | 26 | 27 | {% endblock %} 28 | 29 | 30 | {% block content %} 31 | 32 | {% if pizza.id %} 33 |

{% trans 'Update pizza' %} #{{ pizza.id }}

34 | {% else %} 35 |

{% trans 'Create a pizza' %}

36 | {% endif %} 37 | 38 | 39 |
40 | {% csrf_token %} 41 | 42 | 43 | {% if form.errors and form.toppings.errors %} 44 |
45 | 46 |
47 | {{ form.toppings }} 48 | {% for error in form.toppings.errors %} 49 | {{ error }} 50 | {% endfor %} 51 |
52 |
53 | {% else %} 54 |
55 | 56 |
57 | {{ form.toppings }} 58 |
59 |
60 | {% endif %} 61 | 62 |
63 |
64 | 65 |
66 |
67 | 68 |
69 | 70 | 71 | {% endblock %} 72 | 73 | 74 | {% block scripts_end %} 75 | 76 | 77 | 78 | 83 | 84 | < --> 85 | 86 | {% endblock %} 87 | -------------------------------------------------------------------------------- /test_projects/django18/pizzagigi/templates/pizzagigi/pizza_list.html: -------------------------------------------------------------------------------- 1 | {% extends "pizzagigi/base.html" %} 2 | {% load i18n static %} 3 | {% load pizza_tags %} 4 | 5 | 6 | {% block page_title %}{% trans 'Pizzas' %}{% endblock %} 7 | 8 | 9 | {% block content %} 10 | 11 |

{% trans 'Pizzas' %}

12 | 13 | {% if pizzas %} 14 | 19 | {% else %} 20 |

{% trans 'No pizzas found' %}

21 | {% endif %} 22 | 23 | {% if is_paginated %} 24 |
25 |
    26 | {% if page_obj.has_previous %} 27 |
  • «
  • 28 | {% else %} 29 |
  • «
  • 30 | {% endif %} 31 | {% for p in page_obj.paginator.page_range %} 32 | {% ifequal p page_obj.number %} 33 |
  • {{ p }}
  • 34 | {% else %} 35 |
  • {{ p }}
  • 36 | {% endifequal %} 37 | {% empty %} 38 | Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }} 39 | {% endfor %} 40 | {% if page_obj.has_next %} 41 |
  • »
  • 42 | {% else %} 43 |
  • »
  • 44 | {% endif %} 45 |
46 |
47 | {% else %} 48 | 49 | 50 | 51 | {% endif %} 52 | 53 | 54 | {% trans 'Create a pizza' %} 55 | 56 | {% endblock %} 57 | -------------------------------------------------------------------------------- /test_projects/django18/pizzagigi/templates/pizzagigi/pizza_updated.html: -------------------------------------------------------------------------------- 1 | {% extends "pizzagigi/base.html" %} 2 | {% load i18n static %} 3 | 4 | 5 | {% block page_title %}{% trans 'Pizza updated' %}{% endblock %} 6 | 7 | 8 | {% block breadcrumbs %} 9 | 10 | 15 | 16 | {% endblock %} 17 | 18 | 19 | {% block content %} 20 | 21 |

{% trans 'Pizza updated' %}

22 | 23 | 24 |

{% trans 'Your pizza was updated' %}

25 | 26 | {% trans 'View pizzas' %} 27 | 28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /test_projects/django18/pizzagigi/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_projects/django18/pizzagigi/templatetags/__init__.py -------------------------------------------------------------------------------- /test_projects/django18/pizzagigi/templatetags/pizza_tags.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django import template 5 | 6 | from pizzagigi.models import show_topping 7 | 8 | 9 | register = template.Library() 10 | 11 | 12 | def decode_pie(ingredients): 13 | """ 14 | Decode pizza pie toppings 15 | """ 16 | decoded = [show_topping(t) for t in ingredients] 17 | decoded.sort() 18 | return ', '.join(decoded) 19 | 20 | register.filter('decode_pie', decode_pie) 21 | 22 | 23 | def decode_topping(ingredient): 24 | """ 25 | Decode a single pizza pie topping 26 | """ 27 | return show_topping(ingredient) 28 | 29 | register.filter('decode_topping', decode_topping) 30 | -------------------------------------------------------------------------------- /test_projects/django18/pizzagigi/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | import json 5 | 6 | from django.core import serializers 7 | from django.core.urlresolvers import reverse 8 | from django.test import SimpleTestCase, TestCase 9 | from django.utils.datastructures import MultiValueDict 10 | from django.utils.http import urlencode 11 | 12 | from .models import Pizza, show_topping 13 | 14 | 15 | class PizzaListViewTestCase(TestCase): 16 | 17 | def test_no_pizzas(self): 18 | p = Pizza.objects.all() 19 | self.assertEqual(len(p), 0, 'Test requires no pizzas') 20 | response = self.client.get(reverse('pizza:list')) 21 | self.assertEqual(response.status_code, 200) 22 | self.assertTrue('No pizzas found' in response.content.decode('utf-8')) 23 | 24 | def test_many_pizzas(self): 25 | NUM_PIZZAS = 30 26 | pizzas = [] 27 | for n in range(NUM_PIZZAS): 28 | p = Pizza.objects.create(toppings=[Pizza.PEPPERONI]) 29 | pizzas.append(p) 30 | 31 | self.assertEqual(len(pizzas), NUM_PIZZAS, 'Test requires pizzas') 32 | response = self.client.get(reverse('pizza:list')) 33 | self.assertEqual(response.status_code, 200) 34 | self.assertTrue( 35 | show_topping(Pizza.PEPPERONI) in response.content.decode('utf-8')) 36 | 37 | 38 | class PizzaCreateViewTestCase(TestCase): 39 | 40 | def test_view(self): 41 | response = self.client.get(reverse('pizza:create')) 42 | self.assertEqual(response.status_code, 200) 43 | 44 | def test_creation_single(self): 45 | data = { 46 | 'toppings': [Pizza.BLACK_OLIVES] 47 | } 48 | response = self.client.post( 49 | reverse('pizza:create'), 50 | urlencode(MultiValueDict(data), doseq=True), 51 | content_type='application/x-www-form-urlencoded' 52 | ) 53 | 54 | self.assertEqual(response.status_code, 302) 55 | self.assertRedirects( 56 | response, 57 | 'http://testserver' + reverse('pizza:created')) 58 | p = Pizza.objects.all()[0] 59 | self.assertIn(Pizza.BLACK_OLIVES, p.toppings) 60 | 61 | def test_creation_multiple(self): 62 | data = { 63 | 'toppings': [Pizza.MOZZARELLA, Pizza.PANCETTA] 64 | } 65 | response = self.client.post( 66 | reverse('pizza:create'), 67 | urlencode(MultiValueDict(data), doseq=True), 68 | content_type='application/x-www-form-urlencoded' 69 | ) 70 | self.assertEqual(response.status_code, 302) 71 | self.assertRedirects( 72 | response, 73 | 'http://testserver' + reverse('pizza:created')) 74 | p = Pizza.objects.all()[0] 75 | self.assertIn(Pizza.MOZZARELLA, p.toppings) 76 | self.assertIn(Pizza.PANCETTA, p.toppings) 77 | 78 | 79 | class PizzaDetailViewTestCase(TestCase): 80 | 81 | def setUp(self): 82 | self.pizza = Pizza(toppings=[Pizza.EGG]) 83 | self.pizza.save() 84 | 85 | def test_view(self): 86 | response = self.client.get( 87 | reverse('pizza:detail', args=[self.pizza.id])) 88 | self.assertEqual(response.status_code, 200) 89 | self.assertEqual(response.context['object'], self.pizza) 90 | 91 | 92 | class PizzaUpdateViewTestCase(TestCase): 93 | 94 | def setUp(self): 95 | self.pizza = Pizza(toppings=[Pizza.MUSHROOMS, Pizza.TOMATO]) 96 | self.pizza.save() 97 | 98 | def test_change_toppings(self): 99 | data = { 100 | 'toppings': [Pizza.CHEDDAR_CHEESE, Pizza.MUSHROOMS] 101 | } 102 | response = self.client.post( 103 | reverse('pizza:update', args=[self.pizza.id]), 104 | urlencode(MultiValueDict(data), doseq=True), 105 | content_type='application/x-www-form-urlencoded') 106 | self.assertEqual(response.status_code, 302) 107 | self.assertRedirects( 108 | response, 109 | 'http://testserver' + reverse('pizza:updated')) 110 | p = Pizza.objects.all()[0] 111 | self.assertTrue(Pizza.CHEDDAR_CHEESE in p.toppings) 112 | self.assertTrue(Pizza.MUSHROOMS in p.toppings) 113 | self.assertFalse(Pizza.TOMATO in p.toppings) 114 | 115 | 116 | class PizzaDeleteViewTestCase(TestCase): 117 | 118 | def setUp(self): 119 | self.pizza = Pizza(toppings=[Pizza.PROSCIUTTO_CRUDO]) 120 | self.pizza.save() 121 | 122 | def test_delete_pizza(self): 123 | response = self.client.post( 124 | reverse('pizza:delete', args=[self.pizza.id])) 125 | self.assertEqual(response.status_code, 302) 126 | self.assertRedirects( 127 | response, 128 | 'http://testserver' + reverse('pizza:deleted')) 129 | pl = Pizza.objects.all() 130 | self.assertEqual(len(pl), 0) 131 | 132 | 133 | class PizzaModelTestCase(SimpleTestCase): 134 | 135 | def test_show_topping(self): 136 | for k, v in Pizza.TOPPING_CHOICES: 137 | topping_name = show_topping(k) 138 | self.assertEqual(topping_name, v) 139 | 140 | 141 | class PizzaCozyTestCase(TestCase): 142 | """Serialzer tests for dumpdata operations""" 143 | 144 | def setUp(self): 145 | self.toppings_1 = [ 146 | Pizza.ANCHOVIES, 147 | Pizza.BLACK_OLIVES, 148 | Pizza.CHEDDAR_CHEESE, 149 | ] 150 | self.pizza_1 = Pizza.objects.create(toppings=self.toppings_1) 151 | self.toppings_2 = [ 152 | Pizza.TOMATO, 153 | Pizza.MOZZARELLA, 154 | ] 155 | self.pizza_2 = Pizza.objects.create(toppings=self.toppings_2) 156 | 157 | def test_dumpdata_dumps_json(self): 158 | """JSON can handle a native list type not only strings""" 159 | q = Pizza.objects.all() 160 | output = serializers.serialize('json', q) 161 | 162 | js = json.loads(output) 163 | 164 | self.assertTrue(isinstance(js, list)) 165 | ingredients = [] 166 | for i, __ in enumerate(js): 167 | ingredients.extend(js[i]['fields']['toppings']) 168 | 169 | for topping in list(self.toppings_1 + self.toppings_2): 170 | self.assertIn(topping, ingredients) 171 | -------------------------------------------------------------------------------- /test_projects/django18/pizzagigi/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, url 2 | from django.views.generic import TemplateView 3 | 4 | from .views import ( 5 | PizzaCreateView, PizzaDeleteView, PizzaDetailView, PizzaListView, 6 | PizzaUpdateView 7 | ) 8 | 9 | urlpatterns = patterns('', # NOQA 10 | 11 | url(r'^$', PizzaListView.as_view(), name='list'), 12 | url(r'^create/$', PizzaCreateView.as_view(), name='create'), 13 | url(r'^created/$', TemplateView.as_view( 14 | template_name='pizzagigi/pizza_created.html'), name='created'), 15 | url(r'^detail/(?P[0-9]*)$', PizzaDetailView.as_view(), name='detail'), 16 | url(r'^update/(?P[0-9]*)$', PizzaUpdateView.as_view(), name='update'), 17 | url(r'^updated/$', TemplateView.as_view( 18 | template_name='pizzagigi/pizza_updated.html'), name='updated'), 19 | url(r'^delete/(?P[0-9]*)$', PizzaDeleteView.as_view(), name='delete'), 20 | url(r'^deleted/$', TemplateView.as_view( 21 | template_name='pizzagigi/pizza_deleted.html'), name='deleted'), 22 | 23 | ) 24 | -------------------------------------------------------------------------------- /test_projects/django18/pizzagigi/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.core.urlresolvers import reverse_lazy 5 | from django.views.generic import ( 6 | CreateView, DetailView, DeleteView, ListView, UpdateView) 7 | from django.utils.encoding import force_text 8 | 9 | from .models import Pizza 10 | 11 | 12 | class PizzaListView(ListView): 13 | 14 | queryset = Pizza.objects.order_by('-id') 15 | context_object_name = 'pizzas' 16 | paginate_by = 10 17 | 18 | 19 | class PizzaCreateView(CreateView): 20 | 21 | model = Pizza 22 | fields = ['toppings'] 23 | success_url = reverse_lazy('pizza:created') 24 | 25 | 26 | class PizzaDetailView(DetailView): 27 | 28 | model = Pizza 29 | 30 | 31 | class PizzaUpdateView(UpdateView): 32 | 33 | model = Pizza 34 | fields = ['toppings'] 35 | success_url = reverse_lazy('pizza:updated') 36 | 37 | 38 | class PizzaDeleteView(DeleteView): 39 | 40 | model = Pizza 41 | success_url = reverse_lazy('pizza:deleted') 42 | 43 | def get_success_url(self): 44 | return force_text(self.success_url) 45 | -------------------------------------------------------------------------------- /test_suite/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinwong-ca/django-select-multiple-field/9146a0e450577d0178034dfe6d53334fc14d88af/test_suite/__init__.py -------------------------------------------------------------------------------- /test_suite/settings_for_tests.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 5 | 6 | SECRET_KEY = 'sLtk4Vv6Ncc5VhM9PzJtvhpHu6HP3k9v6he2JPWnQ2GmjWTNpZwBqe6NvSkgW5e' 7 | 8 | USE_I18N = True 9 | LANGUAGE_CODE = 'en' 10 | 11 | DATABASES = { 12 | 'default': { 13 | 'ENGINE': 'django.db.backends.sqlite3', 14 | 'NAME': ':memory:' 15 | } 16 | } 17 | 18 | INSTALLED_APPS = [ 19 | 'django.contrib.contenttypes', 20 | 'django.contrib.auth', 21 | 'django.contrib.admin', 22 | 'django.contrib.sessions', 23 | 'django.contrib.messages', 24 | 'django.contrib.staticfiles', 25 | ] 26 | -------------------------------------------------------------------------------- /test_suite/test_codecs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.test import SimpleTestCase 5 | 6 | from select_multiple_field.codecs import ( 7 | decode_csv_to_list, encode_list_to_csv) 8 | 9 | 10 | class CodecTestCase(SimpleTestCase): 11 | 12 | def setUp(self): 13 | self.choices = ( 14 | ('a', 'Alpha'), 15 | ('b', 'Bravo'), 16 | ('c', 'Charlie'), 17 | ) 18 | self.test_list = ['a', 'b', 'c'] 19 | self.test_encoded = 'a,b,c' 20 | self.wild_delimiter = 'シ' 21 | self.test_encoded_alt = 'aシbシc' 22 | 23 | def test_decoder(self): 24 | decoded = decode_csv_to_list(self.test_encoded) 25 | self.assertEqual(decoded, self.test_list) 26 | decoded = decode_csv_to_list(self.test_encoded[0:1]) 27 | self.assertEqual(decoded, self.test_list[0:1]) 28 | 29 | def test_decoder_on_empty_string(self): 30 | decoded = decode_csv_to_list('') 31 | self.assertEqual(decoded, []) 32 | 33 | def test_decoder_on_single_encoded_character(self): 34 | single_encoded = self.choices[1][0] 35 | decoded = decode_csv_to_list(single_encoded) 36 | self.assertEqual(decoded, [single_encoded]) 37 | 38 | def test_decoder_deduplicates(self): 39 | decoded = decode_csv_to_list(self.test_encoded + ',b,c,c') 40 | self.assertEqual(decoded, self.test_list) 41 | 42 | def test_decoder_delimiter(self): 43 | with self.settings(SELECTMULTIPLEFIELD_DELIMITER=self.wild_delimiter): 44 | decoded = decode_csv_to_list(self.test_encoded_alt) 45 | self.assertEqual(decoded, self.test_list) 46 | 47 | def test_encoder(self): 48 | encoded = encode_list_to_csv(self.test_list) 49 | self.assertEqual(encoded, self.test_encoded) 50 | encoded = encode_list_to_csv(self.test_list[0:1]) 51 | self.assertEqual(encoded, self.test_encoded[0:1]) 52 | 53 | def test_encoder_on_empty_list(self): 54 | encoded = encode_list_to_csv([]) 55 | self.assertEqual(encoded, '') 56 | 57 | def test_encoder_deduplicates(self): 58 | encoded = encode_list_to_csv(self.test_list * 3) 59 | self.assertEqual(encoded, self.test_encoded) 60 | 61 | def test_encoder_delimiter(self): 62 | with self.settings(SELECTMULTIPLEFIELD_DELIMITER=self.wild_delimiter): 63 | encoded = encode_list_to_csv(self.test_list) 64 | self.assertEqual(encoded, self.test_encoded_alt) 65 | -------------------------------------------------------------------------------- /test_suite/test_forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | import string 5 | 6 | from django.forms import fields 7 | from django.test import SimpleTestCase 8 | 9 | from select_multiple_field.codecs import encode_list_to_csv 10 | from select_multiple_field.forms import ( 11 | DEFAULT_MAX_CHOICES_ATTR, SelectMultipleFormField) 12 | from select_multiple_field.widgets import SelectMultipleField 13 | 14 | 15 | class SelectMultipleFormFieldTestCase(SimpleTestCase): 16 | 17 | def setUp(self): 18 | self.choices = tuple([(c, c) for c in string.ascii_letters]) 19 | self.choices_list = [c[0] for c in self.choices[0:len(self.choices)]] 20 | 21 | def test_instantiation(self): 22 | ff = SelectMultipleFormField() 23 | self.assertIsInstance(ff, fields.Field) 24 | self.assertIsInstance(ff, fields.MultipleChoiceField) 25 | 26 | def test_widget_class(self): 27 | ff = SelectMultipleFormField() 28 | self.assertIsInstance(ff.widget, SelectMultipleField) 29 | 30 | def test_field_to_python_value_is_none(self): 31 | """Widget may return None as value for missing key in POST""" 32 | ff = SelectMultipleFormField() 33 | self.assertEqual(ff.to_python(None), []) 34 | 35 | def test_field_to_python_value_is_empty_string(self): 36 | """Widget may return empty string as value for key in POST""" 37 | ff = SelectMultipleFormField() 38 | self.assertEqual(ff.to_python(''), []) 39 | 40 | def test_field_to_python_value_is_simple_string(self): 41 | """Widget may return simple string as value for key in POST""" 42 | ff = SelectMultipleFormField() 43 | simple = self.choices_list[1] 44 | self.assertEqual(ff.to_python(simple), [simple]) 45 | 46 | def test_field_to_python_value_is_encoded_string(self): 47 | """Widget may return encoded string as value for key in POST""" 48 | ff = SelectMultipleFormField() 49 | for i, v in enumerate(self.choices_list): 50 | subset = self.choices_list[0: i] 51 | encoded = encode_list_to_csv(subset) 52 | self.assertEqual(ff.to_python(encoded), sorted(subset)) 53 | 54 | def test_widget_attrs_size(self): 55 | """Widget passed size info""" 56 | fake_widget = 'Fake widget' 57 | # 58 | # Case #1: Default size 4 not passed to widget 59 | # 60 | ff = SelectMultipleFormField() 61 | self.assertEqual(ff.size, 4) 62 | self.assertNotIn('size', ff.widget_attrs(fake_widget)) 63 | # 64 | # Case #2: Any other size passed to widget 65 | # 66 | NON_DEFAULT_SIZE = 8 67 | ff = SelectMultipleFormField(size=NON_DEFAULT_SIZE) 68 | self.assertEqual(ff.size, NON_DEFAULT_SIZE) 69 | self.assertEqual(ff.widget_attrs(fake_widget).get( 70 | 'size'), str(NON_DEFAULT_SIZE)) 71 | 72 | def test_widget_attrs_max_choices(self): 73 | """Widget passed max_choices information""" 74 | fake_widget = 'Fake widget' 75 | # 76 | # Case #1: Optional max_choices not sent to widget 77 | # 78 | ff = SelectMultipleFormField() 79 | self.assertTrue(ff.max_choices is None) 80 | self.assertNotIn('data-max-choices', ff.widget_attrs(fake_widget)) 81 | # 82 | # Case #2: When set, max_coices passed as data attribute 83 | # 84 | MAX_CHOICES = 3 85 | ff = SelectMultipleFormField(max_choices=MAX_CHOICES) 86 | self.assertEqual(ff.max_choices, MAX_CHOICES) 87 | self.assertEqual(ff.widget_attrs(fake_widget).get( 88 | DEFAULT_MAX_CHOICES_ATTR), str(MAX_CHOICES)) 89 | -------------------------------------------------------------------------------- /test_suite/test_validators.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | import string 5 | 6 | from django.core.exceptions import ValidationError 7 | from django.test import SimpleTestCase 8 | 9 | from select_multiple_field.codecs import encode_list_to_csv 10 | from select_multiple_field.models import SelectMultipleField 11 | from select_multiple_field.validators import ( 12 | MaxChoicesValidator, MaxLengthValidator) 13 | 14 | 15 | class SelectMultipleFieldValidatorsTestCase(SimpleTestCase): 16 | 17 | def setUp(self): 18 | self.choices = tuple([(c, c) for c in string.ascii_letters]) 19 | self.choices_list = [c[0] for c in self.choices[0:len(self.choices)]] 20 | 21 | def test_max_choices_single(self): 22 | item = SelectMultipleField( 23 | choices=self.choices, max_length=254, max_choices=1) 24 | self.assertEqual(item.max_choices, 1) 25 | self.assertIsInstance(item.validators[1], MaxChoicesValidator) 26 | self.assertIs(item.run_validators(self.choices_list[0:1]), None) 27 | 28 | def test_max_choices_many(self): 29 | for n in range(2, len(self.choices_list)): 30 | many_choices = self.choices_list[0:n] 31 | many_choices_len = len(many_choices) 32 | item = SelectMultipleField( 33 | choices=self.choices, 34 | max_length=254, 35 | max_choices=many_choices_len) 36 | self.assertEqual(item.max_choices, many_choices_len) 37 | self.assertIsInstance(item.validators[1], MaxChoicesValidator) 38 | self.assertIs(item.run_validators(many_choices), None) 39 | 40 | def test_max_choices_validationerror_single(self): 41 | item = SelectMultipleField( 42 | choices=self.choices, max_length=10, max_choices=1) 43 | self.assertEqual(item.max_choices, 1) 44 | self.assertIsInstance(item.validators[1], MaxChoicesValidator) 45 | two_choices = self.choices_list[0:2] 46 | with self.assertRaises(ValidationError) as cm: 47 | item.run_validators(value=two_choices) 48 | 49 | self.assertEqual( 50 | cm.exception.messages[0], 51 | MaxChoicesValidator.message % { 52 | 'limit_value': 1, 53 | 'show_value': len(two_choices) 54 | } 55 | ) 56 | 57 | def test_max_choices_validationerror_many(self): 58 | for n in range(3, len(self.choices_list)): 59 | test_max_choices = n - 1 # One less than encoded list len 60 | item = SelectMultipleField( 61 | choices=self.choices, 62 | max_length=254, 63 | max_choices=test_max_choices) 64 | self.assertEqual(item.max_choices, test_max_choices) 65 | self.assertIsInstance(item.validators[1], MaxChoicesValidator) 66 | many_choices = self.choices_list[0:n] 67 | many_choices_len = len(many_choices) 68 | self.assertTrue(many_choices_len > test_max_choices) 69 | with self.assertRaises(ValidationError) as cm: 70 | item.run_validators(value=many_choices) 71 | 72 | self.assertEqual( 73 | cm.exception.messages[0], 74 | MaxChoicesValidator.message % { 75 | 'limit_value': item.max_choices, 76 | 'show_value': many_choices_len} 77 | ) 78 | 79 | def test_max_length_single(self): 80 | item = SelectMultipleField(choices=self.choices, max_length=1) 81 | self.assertEqual(item.max_length, 1) 82 | self.assertIsInstance(item.validators[0], MaxLengthValidator) 83 | choice = self.choices_list[0:1] 84 | self.assertIs(item.run_validators(value=choice), None) 85 | 86 | def test_max_length_many(self): 87 | for n in range(2, len(self.choices_list)): 88 | many_choices = self.choices_list[0:n] 89 | encoded_choices_len = len(encode_list_to_csv(many_choices)) 90 | item = SelectMultipleField( 91 | choices=self.choices, max_length=encoded_choices_len) 92 | self.assertEqual(item.max_length, encoded_choices_len) 93 | self.assertIsInstance(item.validators[0], MaxLengthValidator) 94 | self.assertIs(item.run_validators(value=many_choices), None) 95 | 96 | def test_max_length_validationerror_single(self): 97 | item = SelectMultipleField(choices=self.choices, max_length=1) 98 | self.assertEqual(item.max_length, 1) 99 | self.assertIsInstance(item.validators[0], MaxLengthValidator) 100 | two_choices = self.choices_list[0:2] 101 | with self.assertRaises(ValidationError) as cm: 102 | item.run_validators(value=two_choices) 103 | 104 | self.assertEqual( 105 | cm.exception.messages[0], 106 | MaxLengthValidator.message % {'limit_value': 1, 'show_value': 3} 107 | ) 108 | 109 | def test_max_length_validationerror_many(self): 110 | for n in range(2, len(self.choices_list)): 111 | test_max_length = 2 * n - 2 # One less than encoded list len 112 | item = SelectMultipleField( 113 | choices=self.choices, max_length=test_max_length) 114 | self.assertEqual(item.max_length, test_max_length) 115 | self.assertIsInstance(item.validators[0], MaxLengthValidator) 116 | many_choices = self.choices_list[0:n] 117 | many_choices_len = len(encode_list_to_csv(many_choices)) 118 | self.assertTrue(many_choices_len > test_max_length) 119 | with self.assertRaises(ValidationError) as cm: 120 | item.run_validators(value=many_choices) 121 | 122 | self.assertEqual( 123 | cm.exception.messages[0], 124 | MaxLengthValidator.message % { 125 | 'limit_value': item.max_length, 126 | 'show_value': many_choices_len} 127 | ) 128 | -------------------------------------------------------------------------------- /test_suite/test_widgets.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.forms import widgets 5 | from django.test import SimpleTestCase 6 | from django.utils.datastructures import MultiValueDict 7 | 8 | from select_multiple_field.widgets import ( 9 | HTML_ATTR_CLASS, SelectMultipleField) 10 | 11 | 12 | class SelectMultipleFieldTestCase(SimpleTestCase): 13 | 14 | def setUp(self): 15 | self.choices = ( 16 | ('a', 'Alpha'), 17 | ('b', 'Bravo'), 18 | ('c', 'Charlie'), 19 | ) 20 | 21 | def test_instantiation(self): 22 | w = SelectMultipleField() 23 | self.assertIsInstance(w, widgets.SelectMultiple) 24 | 25 | def test_has_select_multiple_class(self): 26 | """Rendered widget has a useful HTML class attribute""" 27 | w = SelectMultipleField() 28 | tag = w.render('test', self.choices[1][0], choices=self.choices) 29 | self.assertEqual(tag.count(HTML_ATTR_CLASS), 1) 30 | 31 | def test_html_attr_class_settable(self): 32 | """Rendered widget can override HTML class attribute""" 33 | CUSTOM_HTML_CLASS = 'myowncss' 34 | attrs = {'class': CUSTOM_HTML_CLASS} 35 | w = SelectMultipleField() 36 | tag = w.render('test', self.choices[1][0], attrs, self.choices) 37 | self.assertEqual(tag.count(CUSTOM_HTML_CLASS), 1) 38 | self.assertEqual(tag.count(HTML_ATTR_CLASS), 0) 39 | 40 | def test_value_from_datadict(self): 41 | """Widget generates expected Python list-like object or None""" 42 | # 43 | # I know that this tests Django code. Humor me pls. 44 | # 45 | w = SelectMultipleField() 46 | name = 'test' 47 | data = { 48 | name: [self.choices[0][0], self.choices[2][0]] 49 | } 50 | # 51 | # dict miss returns None 52 | # 53 | obj = w.value_from_datadict({}, None, name) 54 | self.assertIs(obj, None) 55 | # 56 | # Plain dict returns obj in value, usually a list 57 | # 58 | obj = w.value_from_datadict(data, None, name) 59 | self.assertIsInstance(obj, list) 60 | self.assertIn(self.choices[0][0], obj) 61 | self.assertNotIn(self.choices[1][0], obj) 62 | self.assertIn(self.choices[2][0], obj) 63 | # 64 | # MultiValueDict are generated from WSGIRequest 65 | # 66 | data_obj = MultiValueDict(data) 67 | obj = w.value_from_datadict(data_obj, None, name) 68 | self.assertIsInstance(obj, list) 69 | self.assertIn(self.choices[0][0], obj) 70 | self.assertNotIn(self.choices[1][0], obj) 71 | self.assertIn(self.choices[2][0], obj) 72 | # 73 | # MergeDict are generated from QueryDict which are subclasses of 74 | # MultiValueDict 75 | # 76 | # data_obj = MergeDict(MultiValueDict(data)) 77 | # obj = w.value_from_datadict(data_obj, None, name) 78 | # self.assertIsInstance(obj, list) 79 | # self.assertIn(self.choices[0][0], obj) 80 | # self.assertNotIn(self.choices[1][0], obj) 81 | # self.assertIn(self.choices[2][0], obj) 82 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py26-dj14, py26-dj14-south, 4 | py27, py27-dj14, py27-dj14-south, py27-dj17, py27-dj18, py27-dj19, 5 | py27-djangomaster, 6 | py32, py32-dj17, py32-dj18, 7 | py33, py33-dj17, py33-dj18, 8 | py34, py34-dj17, py34-dj18, py34-dj19, 9 | py35, py35-dj19, 10 | py35-djangomaster 11 | pip_pre = True 12 | 13 | [south] 14 | deps = 15 | south>=1.0 16 | 17 | [django1.4] 18 | deps = 19 | Django>=1.4,<1.5 20 | 21 | [django1.7] 22 | deps = 23 | Django>=1.7,<1.8 24 | 25 | [django1.8] 26 | deps = 27 | Django>=1.8,<1.9 28 | 29 | [django1.9] 30 | deps = 31 | Django>=1.9,<2.0 32 | 33 | [djangomaster] 34 | deps = 35 | https://github.com/django/django/zipball/master 36 | 37 | [testenv] 38 | commands = 39 | python setup.py test 40 | python setup.py test_demo 41 | 42 | [testenv:py26-dj14] 43 | basepython = python2.6 44 | deps = 45 | {[django1.4]deps} 46 | unittest2 47 | 48 | [testenv:py26-dj14-south] 49 | basepython = python2.6 50 | deps = 51 | {[django1.4]deps} 52 | {[south]deps} 53 | unittest2 54 | 55 | [testenv:py27] 56 | deps = 57 | {[django1.8]deps} 58 | 59 | [testenv:py27-dj14] 60 | basepython = python2.7 61 | deps = 62 | {[django1.4]deps} 63 | 64 | [testenv:py27-dj14-south] 65 | basepython = python2.7 66 | deps = 67 | {[django1.4]deps} 68 | {[south]deps} 69 | 70 | [testenv:py27-dj17] 71 | basepython = python2.7 72 | deps = 73 | {[django1.7]deps} 74 | 75 | [testenv:py27-dj18] 76 | basepython = python2.7 77 | deps = 78 | {[django1.8]deps} 79 | 80 | [testenv:py27-dj19] 81 | basepython = python2.7 82 | deps = 83 | {[django1.9]deps} 84 | 85 | [testenv:py27-djangomaster] 86 | recreate=True 87 | basepython = python2.7 88 | deps = 89 | {[djangomaster]deps} 90 | 91 | [testenv:py32] 92 | deps = 93 | {[django1.8]deps} 94 | 95 | [testenv:py32-dj17] 96 | basepython = python3.2 97 | deps = 98 | {[django1.7]deps} 99 | 100 | [testenv:py32-dj18] 101 | basepython = python3.2 102 | deps = 103 | {[django1.8]deps} 104 | 105 | [testenv:py33] 106 | deps = 107 | {[django1.8]deps} 108 | 109 | [testenv:py33-dj17] 110 | basepython = python3.3 111 | deps = 112 | {[django1.7]deps} 113 | 114 | [testenv:py33-dj18] 115 | basepython = python3.3 116 | deps = 117 | {[django1.8]deps} 118 | 119 | [testenv:py34] 120 | deps = 121 | {[django1.8]deps} 122 | 123 | [testenv:py34-dj17] 124 | basepython = python3.4 125 | deps = 126 | {[django1.7]deps} 127 | 128 | [testenv:py34-dj18] 129 | basepython = python3.4 130 | deps = 131 | {[django1.8]deps} 132 | 133 | [testenv:py34-dj19] 134 | basepython = python3.4 135 | deps = 136 | {[django1.9]deps} 137 | 138 | [testenv:py35] 139 | basepython = python3.5 140 | deps = 141 | {[django1.8]deps} 142 | 143 | [testenv:py35-dj19] 144 | basepython = python3.5 145 | deps = 146 | {[django1.9]deps} 147 | 148 | [testenv:py35-djangomaster] 149 | recreate=True 150 | basepython = python3.5 151 | deps = 152 | {[djangomaster]deps} 153 | --------------------------------------------------------------------------------