├── .gitignore ├── .travis.yml ├── AUTHORS ├── CHANGELOG.txt ├── DESCRIPTION ├── LICENSE ├── MANIFEST.in ├── README.rst ├── docs └── README.md ├── manage.py ├── requirements.txt ├── review ├── __init__.py ├── admin.py ├── compat.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20180328_0620.py │ └── __init__.py ├── models.py ├── south_migrations │ ├── 0001_initial.py │ ├── 0002_auto__add_field_review_average_rating.py │ ├── 0003_auto__add_field_ratingcategory_identifier.py │ ├── 0004_auto__del_ratingcategory__del_ratingcategorytranslation__del_field_rat.py │ ├── 0005_auto__add_ratingcategory__add_ratingcategorytranslation__add_unique_ra.py │ ├── 0006_auto__add_field_review_extra_content_type__add_field_review_extra_obje.py │ ├── 0007_auto__add_ratingcategorychoicetranslation__add_unique_ratingcategorych.py │ ├── 0008_auto__chg_field_ratingcategorychoice_value.py │ ├── 0009_auto__add_field_ratingcategorytranslation_question.py │ └── __init__.py ├── templates │ └── review │ │ ├── partials │ │ └── category_averages.html │ │ ├── review_confirm_delete.html │ │ ├── review_detail.html │ │ ├── review_form.html │ │ └── review_list.html ├── templatetags │ ├── __init__.py │ └── review_tags.py ├── tests │ ├── __init__.py │ ├── forms_tests.py │ ├── models_tests.py │ ├── review_tags_tests.py │ ├── settings.py │ ├── test_app │ │ ├── __init__.py │ │ ├── forms.py │ │ ├── models.py │ │ └── templates │ │ │ ├── 400.html │ │ │ ├── 500.html │ │ │ └── base.html │ ├── test_settings.py │ ├── urls.py │ └── views_tests.py ├── urls.py └── views.py ├── runtests.py ├── setup.py ├── test_requirements.txt └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info/ 2 | *.pyc 3 | *coverage/ 4 | .tox/ 5 | .coverage 6 | app_media/ 7 | app_static/ 8 | build/ 9 | db.sqlite 10 | dist/ 11 | docs/_build/ 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | install: pip install -r requirements.txt --use-mirrors 5 | script: python review/tests/runtests.py 6 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Current or previous core committers 2 | 3 | Tobias Lorenz 4 | 5 | Contributors (in alphabetical order) 6 | 7 | * Aliaksei Urbanski (Jamim) 8 | * Jonathan Kellenberg (losttrekker) 9 | * Nikolaus Schlemm (nschlemm) 10 | * Your name could stand here :) 11 | -------------------------------------------------------------------------------- /CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | === (ongoing) === 2 | 3 | === 1.10.0 === 4 | 5 | - Adds missing migrations 6 | 7 | === 1.9.7 === 8 | 9 | - Renamed content type variable in create view 10 | 11 | 12 | === 1.9.7 === 13 | 14 | - Renamed content type variable in create view 15 | 16 | === 1.9.6 === 17 | 18 | - Only aggregating category_averages if there are any for the given review 19 | 20 | === 1.9.5 === 21 | 22 | - Fixed rating category choice admin 23 | 24 | === 1.9.4 === 25 | 26 | - prepared app for Python 3.5 27 | 28 | === 1.9.3 === 29 | 30 | - prepared app for Django 1.9 31 | 32 | === 1.9.2 === 33 | 34 | - Fixed REVIEW_FORM_CHOICE_WIDGET setting 35 | 36 | === 1.9.1 === 37 | 38 | - Prepared south migrations for custom user models 39 | 40 | === 1.9 === 41 | 42 | - Added custom user model 43 | - Requirement update 44 | 45 | === 1.8.1 === 46 | 47 | - Added more dependencies to setup.py 48 | 49 | === 1.8 === 50 | 51 | - added get_reviews template tag 52 | - added get_review_average template tag 53 | 54 | === 1.7.4 === 55 | 56 | - counting only valid ratings for category average 57 | 58 | === 1.7.3 === 59 | 60 | - fixed empty categories raising an exception for not being in the averages 61 | dictionary in the render_category_averages template tag. 62 | 63 | === 1.7.2 === 64 | 65 | - fixed iteration over dictionary 66 | 67 | === 1.7.1 === 68 | 69 | - fixed query for ratings, that did not filter for review 70 | - fixed bug in render_category_averages template tag 71 | 72 | === 1.7 === 73 | 74 | - added question field to RatingCategory to allow showing more explainatory 75 | information. The name field remains as label for the form's category fields 76 | as short version of the question. 77 | - added get_review_count template tag 78 | - added render_category_averages template tag 79 | - fixed average calculation for 0 values 80 | 81 | === 1.6 === 82 | 83 | - added user_has_reviewed template tag 84 | - allow callables for REVIEW_UPDATE_SUCCESS_URL 85 | - made ordering of custom choices make more sense 86 | - added setting to override the default form select widget 87 | 88 | === 1.5 === 89 | 90 | - re-implemented average calculation 91 | - added total_review_average template tag 92 | - added RatingCategoryChoice model and added counts_for_average to 93 | RatingCategory. 94 | 95 | === 1.4 === 96 | 97 | - Added optional extra content object to review model 98 | 99 | === 1.3 === 100 | 101 | - Added optional success URL setting for update and creation views 102 | 103 | === 1.2.1 === 104 | 105 | - Added success URL setting for delete view 106 | 107 | === 1.2 === 108 | 109 | - Added review delete view 110 | 111 | === 1.1 === 112 | 113 | - Re-arranged migrations 114 | 115 | === 1.0 === 116 | 117 | - BACKWARDS INCOMPATIBLE: Moved from simpletranslation to hvad 118 | 119 | === 0.4 === 120 | 121 | - added identifier field for rating categories 122 | 123 | === 0.3 === 124 | 125 | - added setting to use a custom form 126 | 127 | === 0.2.1 === 128 | 129 | - Fixed form 130 | - Added reviewed item to permission function 131 | 132 | === 0.2 === 133 | 134 | - Added RatingAdmin 135 | - Added average_rating field to Review model 136 | 137 | === 0.1.1 === 138 | - Added is_editable function to review model 139 | 140 | === 0.1 === 141 | - Created basic app 142 | 143 | 144 | # Suggested file syntax: 145 | # 146 | # === (ongoing) === 147 | # - this is always on top of the file 148 | # - when you release a new version, you rename the last `(ongoing)` to the new 149 | # version and add a new `=== (ongoing) ===` to the top of the file 150 | # 151 | # === 1.0 === 152 | # - a major version is created when the software reached a milestone and is 153 | # feature complete 154 | # 155 | # === 0.2 === 156 | # - a minor version is created when new features or significant changes have 157 | # been made to the software. 158 | # 159 | # === 0.1.1 == 160 | # - for bugfix releases, fixing typos in the docs, restructuring things, simply 161 | # anything that doesn't really change the behaviour of the software you 162 | # might use the third digit which is also sometimes called the build number. 163 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | A reusable Django app that lets users write reviews for any model 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2013 Daniel Kaufhold 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS 2 | include LICENSE 3 | include DESCRIPTION 4 | include CHANGELOG.txt 5 | include README.md 6 | graft review 7 | global-exclude *.orig *.pyc *.log *.swp 8 | prune review/tests/coverage 9 | prune review/.ropeproject 10 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Django Review 2 | ============= 3 | 4 | A reusable Django app that lets users write reviews for any model 5 | 6 | Installation 7 | ------------ 8 | 9 | To get the latest stable release from PyPi 10 | 11 | .. code-block:: bash 12 | 13 | $ pip install django-review 14 | 15 | To get the latest commit from GitHub 16 | 17 | .. code-block:: bash 18 | 19 | $ pip install -e git+git://github.com/bitmazk/django-review.git#egg=review 20 | 21 | TODO: Describe further installation steps (edit / remove the examples below): 22 | 23 | Add ``review`` to your ``INSTALLED_APPS`` 24 | 25 | .. code-block:: python 26 | 27 | INSTALLED_APPS = ( 28 | ..., 29 | 'hvad', 30 | 'review', 31 | 'user_media', 32 | 'generic_positions', 33 | ) 34 | 35 | Add the ``review`` URLs to your ``urls.py`` 36 | 37 | .. code-block:: python 38 | 39 | urlpatterns = patterns('', 40 | ... 41 | url(r'^review/', include('review.urls')), 42 | ) 43 | 44 | Don't forget to migrate your database 45 | 46 | .. code-block:: bash 47 | 48 | ./manage.py migrate 49 | 50 | 51 | Usage 52 | ----- 53 | 54 | The only step you'll have to take is to link to the review views. For example, 55 | you created a ``Book`` model, which should be reviewed by users. 56 | 57 | Create a button and add some markup like: 58 | 59 | .. code-block:: html 60 | 61 | {% trans "Review this book" %} 62 | 63 | 64 | Ratings & Categories 65 | -------------------- 66 | 67 | To make use of ratings you'll have to create RatingCategory objects. For example, 68 | you want to allow reviews of a hotel booking. You now might add categories like 69 | "room", "service", "food" or whatever. After you have created those categories 70 | in the standard Django admin interface they will appear in your review form. The 71 | average rating of an object is generated by all of its category ratings. 72 | 73 | Template tags 74 | ------------- 75 | 76 | total_review_average 77 | ++++++++++++++++++++ 78 | 79 | For rendering the total review average for any object, you can use the 80 | assignment tag ``total_review_average``. It automatically calculates the 81 | averages of all reviews for the given object and you can specify what range it 82 | should have. The following examples would resemble a percentage or a stars 83 | rating: 84 | 85 | .. code-block:: html 86 | 87 | {% load review_tags %} 88 | {% total_review_average object 100 as percentage %} 89 |

{{ percentage }}% of our users recommended this!

90 | 91 | {% total_review_average object 5 as stars %} 92 |

This object got {{ stars }} out of 5 stars.

93 | 94 | 95 | render_category_averages 96 | ++++++++++++++++++++++++ 97 | 98 | Renders the template ``review/partials/category_averages.html`` to display a 99 | table of categories with their average rating. 100 | Again, you can specify what maximum rating value the averages normalize to. 101 | 102 | .. code-block:: html 103 | 104 | {% load review_tags %} 105 | {% render_category_averages object 100 %} 106 | 107 | 108 | If you had 2 categories, this would per default render to something like the 109 | following example, but you can of course customize the template to your needs. 110 | 111 | .. code-block:: html 112 | 113 | 114 | 115 | 116 | 117 |
Category 1:10.0
Category 2:20.0
Amount of reviews:2
118 | 119 | 120 | get_reviews 121 | +++++++++++ 122 | 123 | An assignment tag, that simply returns the reviews made for the given object. 124 | An example usage would look like this: 125 | 126 | .. code-block:: html 127 | 128 | {% load review_tags %} 129 | 130 | {% get_reviews object as reviews %} 131 | {% for review in reviews %} 132 |

133 | {{ review.get_average_rating }} 134 |

135 |

136 | {% if review.content %} 137 | {{ review.content|truncatewords:'70' }} 138 | {% else %} 139 | Reviewed without description. 140 | {% endif %} 141 | 142 | Review details 143 | {% endfor %} 144 | 145 | 146 | get_review_average 147 | ++++++++++++++++++ 148 | 149 | An assignment tag, that returns the review average for the given object. An 150 | example usage would look like this: 151 | 152 | .. code-block:: html 153 | 154 | {% load review_tags %} 155 | 156 | {% get_review_average object as review_average %} 157 |

This object is rated by {{ review_average }}

158 | 159 | 160 | get_review_count 161 | ++++++++++++++++ 162 | 163 | An assignment tag, that simply returns the amount of reviews made for the 164 | given object. An example usage would look like this: 165 | 166 | .. code-block:: html 167 | 168 | {% load review_tags %} 169 | 170 | {% get_review_count object as review_count %} 171 |

{{ review_count }} users have reviewed this so far.

172 | 173 | 174 | user_has_reviewed 175 | +++++++++++++++++ 176 | 177 | To quickly check if a user has already reviewed the given object, you can use 178 | this template tag. An example usage could be something like this: 179 | 180 | .. code-block:: html 181 | 182 | {% load review_tags %} 183 | {% user_has_reviewed myobject request.user as has_reviewed %} 184 | {% if has_reviewed %} 185 |

Thanks for your opinion!

186 | {% else %} 187 | {% trans "Review this book" %} 188 | {% endif %} 189 | 190 | 191 | Settings 192 | -------- 193 | 194 | Default behaviour: 195 | 196 | * Users can rate form 0 to 5 197 | * Only authenticated users can post a review 198 | * Users can post multiple reviews on one object 199 | * Users can always update their posted reviews 200 | 201 | If you want to change this behaviour, or if you like to add some more 202 | permission checks, read on. 203 | 204 | REVIEW_RATING_CHOICES 205 | +++++++++++++++++++++ 206 | 207 | If you want other rating choices than 0-5, you can define a new tuple, like: 208 | 209 | .. code-block:: python 210 | 211 | REVIEW_RATING_CHOICES = ( 212 | ('1', 'bad'), 213 | ('2', 'average'), 214 | ('3', 'excellent'), 215 | ) 216 | 217 | 218 | REVIEW_ALLOW_ANONYMOUS 219 | ++++++++++++++++++++++ 220 | 221 | Allows anonymous review postings, if set to ``True``. 222 | 223 | 224 | REVIEW_DELETION_SUCCESS_URL 225 | +++++++++++++++++++++++++++ 226 | 227 | Name of the URL to redirect to after deleting a review instance. This could 228 | be your review listing, for example. 229 | 230 | 231 | REVIEW_UPDATE_SUCCESS_URL (optional) 232 | ++++++++++++++++++++++++++++++++++++ 233 | 234 | Default: DetailView of the instance. 235 | 236 | Name of the URL to redirect to after creating/updating a review instance. 237 | This could be your review listing, for example. 238 | 239 | .. code-block:: python 240 | 241 | REVIEW_UPDATE_SUCCESS_URL = 'my_view_name' 242 | 243 | 244 | Or you can also specify a function, that returns the full path. The function 245 | then takes the review as parameter, so you can also access the reviewed item 246 | like follows 247 | 248 | .. code-block:: python 249 | 250 | REVIEW_UPDATE_SUCCESS_URL = lambda review: review.reviewed_item.get_absolute_url() 251 | 252 | 253 | 254 | REVIEW_AVOID_MULTIPLE_REVIEWS 255 | +++++++++++++++++++++++++++++ 256 | 257 | Avoids multiple reviews by one user, if set to ``True``. 258 | Doesn't work with anonymous users. 259 | 260 | 261 | REVIEW_PERMISSION_FUNCTION 262 | ++++++++++++++++++++++++++ 263 | 264 | Custom function to check the user's permission. Use a function and note that 265 | the user and the reviewed item are only parameters. 266 | 267 | .. code-block:: python 268 | 269 | REVIEW_PERMISSION_FUNCTION = lambda u, item: u.get_profile().has_permission(item) 270 | 271 | 272 | REVIEW_UPDATE_PERIOD 273 | ++++++++++++++++++++ 274 | 275 | You can limit the period, in which a user is able to update old reviews. 276 | Make sure to use minutes, e.g. 2880 for 48 hours. 277 | 278 | 279 | REVIEW_CUSTOM_FORM 280 | ++++++++++++++++++ 281 | 282 | You can create your own review form (e.g. if you want to make use of the review 283 | extra info). Just name it. 284 | 285 | .. code-block:: python 286 | 287 | REVIEW_CUSTOM_FORM = 'myapp.forms.MyCustomReviewForm' 288 | 289 | Take a look at the included test app to get an example. 290 | 291 | You can also use a custom form to add another content object to the review 292 | instance. 293 | 294 | 295 | REVIEW_FORM_CHOICE_WIDGET 296 | +++++++++++++++++++++++++ 297 | 298 | If you only want to override Django's default widget for the used 299 | ``ChoiceField``, that is used in the form, you can specify this optional 300 | setting. 301 | 302 | .. code-block:: python 303 | 304 | # this would use a RadioSelect instead of the default Select 305 | REVIEW_FORM_CHOICE_WIDGET = 'django.forms.widgets.RadioSelect' 306 | 307 | 308 | Contribute 309 | ---------- 310 | 311 | If you want to contribute to this project, please perform the following steps 312 | 313 | .. code-block:: bash 314 | 315 | # Fork this repository 316 | # Clone your fork 317 | $ mkvirtualenv -p python2.7 django-review 318 | $ python setup.py install 319 | $ pip install -r dev_requirements.txt 320 | 321 | $ git co -b feature_branch master 322 | # Implement your feature and tests 323 | $ git add . && git commit 324 | $ git push -u origin feature_branch 325 | # Send us a pull request for your feature branch 326 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # How to create your Sphinx documentation 2 | 3 | In order to kickstart your Sphinx documentation, please do the following: 4 | 5 | ## Create virtual environment. 6 | 7 | If you haven't done so already, create a virtual environment for this reusable 8 | app like so: 9 | 10 | mkvirtualenv -p python2.7 django-review 11 | pip install Sphinx 12 | deactivate 13 | workon django-review 14 | sphinx-quickstart 15 | 16 | Answer the questions: 17 | 18 | > Root path for the documentation [.]: 19 | > Separate source and build directories (y/N) [n]: y 20 | > Name prefix for templates and static dir [_]: 21 | > Project name: Django Review 22 | > Author name(s): Daniel Kaufhold 23 | > Project version: 0.1 24 | > Project release [0.1]: 25 | > Source file suffix [.rst]: 26 | > Name of your master document (without suffix) [index]: 27 | > Do you want to use the epub builder (y/N) [n]: 28 | > autodoc: automatically insert docstrings from modules (y/N) [n]: y 29 | > doctest: automatically test code snippets in doctest blocks (y/N) [n]: 30 | > intersphinx: link between Sphinx documentation of different projects (y/N) [n]: y 31 | > todo: write "todo" entries that can be shown or hidden on build (y/N) [n]: y 32 | > coverage: checks for documentation coverage (y/N) [n]: y 33 | > pngmath: include math, rendered as PNG images (y/N) [n]: 34 | > mathjax: include math, rendered in the browser by MathJax (y/N) [n]: 35 | > ifconfig: conditional inclusion of content based on config values (y/N) [n]: y 36 | > viewcode: include links to the source code of documented Python objects (y/N) [n]: y 37 | > Create Makefile? (Y/n) [y]: 38 | > Create Windows command file? (Y/n) [y]: 39 | -------------------------------------------------------------------------------- /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', 'review.tests.settings') 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | django 2 | django-user-media 3 | django-hvad 4 | Pillow 5 | django-libs 6 | -------------------------------------------------------------------------------- /review/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __version__ = '1.10.0' 3 | -------------------------------------------------------------------------------- /review/admin.py: -------------------------------------------------------------------------------- 1 | """Admin classes for the review app.""" 2 | from django.contrib import admin 3 | from django.utils.translation import ugettext_lazy as _ 4 | 5 | from hvad.admin import TranslatableAdmin 6 | 7 | from . import models 8 | 9 | 10 | class RatingAdmin(admin.ModelAdmin): 11 | list_display = ['review', 'category', 'value', ] 12 | raw_id_fields = ['review', ] 13 | 14 | 15 | class ReviewAdmin(admin.ModelAdmin): 16 | list_display = ['reviewed_item', 'user', 'language', 'creation_date'] 17 | 18 | 19 | class ReviewExtraInfoAdmin(admin.ModelAdmin): 20 | list_display = ['type', 'review', 'content_object'] 21 | 22 | 23 | class ReviewCategoryChoiceAdmin(TranslatableAdmin): 24 | list_display = ['ratingcategory', 'value', 'get_label'] 25 | list_select_related = [] 26 | 27 | def get_label(self, obj): 28 | return obj.label 29 | get_label.short_description = _('Label') 30 | 31 | 32 | admin.site.register(models.Rating, RatingAdmin) 33 | admin.site.register(models.RatingCategory, TranslatableAdmin) 34 | admin.site.register(models.Review, ReviewAdmin) 35 | admin.site.register(models.ReviewExtraInfo, ReviewExtraInfoAdmin) 36 | admin.site.register(models.RatingCategoryChoice, ReviewCategoryChoiceAdmin) 37 | -------------------------------------------------------------------------------- /review/compat.py: -------------------------------------------------------------------------------- 1 | try: 2 | from django.contrib.auth import get_user_model 3 | except ImportError: # Django < 1.5 4 | from django.contrib.auth.models import User 5 | else: 6 | User = get_user_model() 7 | 8 | 9 | USER_MODEL = { 10 | 'orm_label': '%s.%s' % (User._meta.app_label, User._meta.object_name), 11 | 'model_label': '%s.%s' % (User._meta.app_label, User._meta.module_name), 12 | 'object_name': User.__name__, 13 | } 14 | -------------------------------------------------------------------------------- /review/forms.py: -------------------------------------------------------------------------------- 1 | """Forms for the ``review`` app.""" 2 | from django import forms 3 | from django.conf import settings 4 | from django.utils.translation import get_language 5 | 6 | from django_libs.loaders import load_member 7 | 8 | from .models import Review, Rating, RatingCategory 9 | 10 | 11 | class ReviewForm(forms.ModelForm): 12 | def __init__(self, reviewed_item, user=None, *args, **kwargs): 13 | self.user = user 14 | self.reviewed_item = reviewed_item 15 | self.widget = load_member( 16 | getattr(settings, 'REVIEW_FORM_CHOICE_WIDGET', 17 | 'django.forms.widgets.Select') 18 | )() 19 | super(ReviewForm, self).__init__(*args, **kwargs) 20 | # Dynamically add fields for each rating category 21 | for category in RatingCategory.objects.all(): 22 | field_name = 'category_{0}'.format(category.pk) 23 | choices = category.get_choices() 24 | self.fields[field_name] = forms.ChoiceField( 25 | choices=choices, label=category.name, 26 | help_text=category.question, 27 | widget=self.widget, 28 | ) 29 | self.fields[field_name].required = category.required 30 | if self.instance.pk: 31 | try: 32 | self.initial.update({ 33 | 'category_{0}'.format(category.pk): Rating.objects.get( 34 | review=self.instance, category=category).value, 35 | }) 36 | except Rating.DoesNotExist: 37 | pass 38 | 39 | def save(self, *args, **kwargs): 40 | if not self.instance.pk: 41 | self.instance.user = self.user 42 | self.instance.reviewed_item = self.reviewed_item 43 | self.instance.language = get_language() 44 | self.instance = super(ReviewForm, self).save(*args, **kwargs) 45 | # Update or create ratings 46 | for field in self.fields: 47 | if field.startswith('category_'): 48 | rating, created = Rating.objects.get_or_create( 49 | review=self.instance, 50 | category=RatingCategory.objects.get( 51 | pk=field.replace('category_', '')), 52 | ) 53 | rating.value = self.cleaned_data[field] 54 | rating.save() 55 | 56 | self.instance.average_rating = self.instance.get_average_rating() 57 | self.instance.save() 58 | return self.instance 59 | 60 | class Meta: 61 | model = Review 62 | fields = ('content', ) 63 | -------------------------------------------------------------------------------- /review/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.5 on 2016-04-12 15:03 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | ('contenttypes', '0002_remove_content_type_name'), 16 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='Rating', 22 | fields=[ 23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 | ('value', models.CharField(blank=True, choices=[(b'5', b'5'), (b'4', b'4'), (b'3', b'3'), (b'2', b'2'), (b'1', b'1')], max_length=20, null=True, verbose_name='Value')), 25 | ], 26 | options={ 27 | 'ordering': ['category', 'review'], 28 | }, 29 | ), 30 | migrations.CreateModel( 31 | name='RatingCategory', 32 | fields=[ 33 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 34 | ('identifier', models.SlugField(blank=True, max_length=32, verbose_name='Identifier')), 35 | ('counts_for_average', models.BooleanField(default=True, verbose_name='Counts for average rating')), 36 | ], 37 | options={ 38 | 'abstract': False, 39 | }, 40 | ), 41 | migrations.CreateModel( 42 | name='RatingCategoryChoice', 43 | fields=[ 44 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 45 | ('value', models.CharField(blank=True, max_length=20, null=True, verbose_name='Value')), 46 | ('ratingcategory', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='review.RatingCategory', verbose_name='Rating category')), 47 | ], 48 | options={ 49 | 'ordering': ('-value',), 50 | }, 51 | ), 52 | migrations.CreateModel( 53 | name='RatingCategoryChoiceTranslation', 54 | fields=[ 55 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 56 | ('label', models.CharField(max_length=128, verbose_name='Label')), 57 | ('language_code', models.CharField(db_index=True, max_length=15)), 58 | ('master', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='review.RatingCategoryChoice')), 59 | ], 60 | options={ 61 | 'managed': True, 62 | 'abstract': False, 63 | 'db_table': 'review_ratingcategorychoice_translation', 64 | 'db_tablespace': '', 65 | 'default_permissions': (), 66 | }, 67 | ), 68 | migrations.CreateModel( 69 | name='RatingCategoryTranslation', 70 | fields=[ 71 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 72 | ('name', models.CharField(max_length=256)), 73 | ('question', models.CharField(blank=True, max_length=512, null=True)), 74 | ('language_code', models.CharField(db_index=True, max_length=15)), 75 | ('master', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='review.RatingCategory')), 76 | ], 77 | options={ 78 | 'managed': True, 79 | 'abstract': False, 80 | 'db_table': 'review_ratingcategory_translation', 81 | 'db_tablespace': '', 82 | 'default_permissions': (), 83 | }, 84 | ), 85 | migrations.CreateModel( 86 | name='Review', 87 | fields=[ 88 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 89 | ('object_id', models.PositiveIntegerField()), 90 | ('content', models.TextField(blank=True, max_length=1024, verbose_name='Content')), 91 | ('language', models.CharField(blank=True, max_length=5, verbose_name='Language')), 92 | ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='Creation date')), 93 | ('average_rating', models.FloatField(default=0, verbose_name='Average rating')), 94 | ('extra_object_id', models.PositiveIntegerField(blank=True, null=True)), 95 | ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), 96 | ('extra_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reviews_attached', to='contenttypes.ContentType')), 97 | ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')), 98 | ], 99 | options={ 100 | 'ordering': ['-creation_date'], 101 | }, 102 | ), 103 | migrations.CreateModel( 104 | name='ReviewExtraInfo', 105 | fields=[ 106 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 107 | ('type', models.CharField(max_length=256, verbose_name='Type')), 108 | ('object_id', models.PositiveIntegerField()), 109 | ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), 110 | ('review', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='review.Review', verbose_name='Review')), 111 | ], 112 | options={ 113 | 'ordering': ['type'], 114 | }, 115 | ), 116 | migrations.AddField( 117 | model_name='rating', 118 | name='category', 119 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='review.RatingCategory', verbose_name='Category'), 120 | ), 121 | migrations.AddField( 122 | model_name='rating', 123 | name='review', 124 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ratings', to='review.Review', verbose_name='Review'), 125 | ), 126 | migrations.AlterUniqueTogether( 127 | name='ratingcategorytranslation', 128 | unique_together=set([('language_code', 'master')]), 129 | ), 130 | migrations.AlterUniqueTogether( 131 | name='ratingcategorychoicetranslation', 132 | unique_together=set([('language_code', 'master')]), 133 | ), 134 | ] 135 | -------------------------------------------------------------------------------- /review/migrations/0002_auto_20180328_0620.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.11 on 2018-03-28 06:20 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | import django.db.models.manager 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('review', '0001_initial'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterModelOptions( 18 | name='ratingcategory', 19 | options={'base_manager_name': '_plain_manager'}, 20 | ), 21 | migrations.AlterModelManagers( 22 | name='ratingcategory', 23 | managers=[ 24 | ('objects', django.db.models.manager.Manager()), 25 | ('_plain_manager', django.db.models.manager.Manager()), 26 | ], 27 | ), 28 | migrations.AlterModelManagers( 29 | name='ratingcategorychoice', 30 | managers=[ 31 | ('objects', django.db.models.manager.Manager()), 32 | ('_plain_manager', django.db.models.manager.Manager()), 33 | ], 34 | ), 35 | migrations.AlterField( 36 | model_name='rating', 37 | name='value', 38 | field=models.CharField(blank=True, choices=[('5', '5'), ('4', '4'), ('3', '3'), ('2', '2'), ('1', '1')], max_length=20, null=True, verbose_name='Value'), 39 | ), 40 | migrations.AlterField( 41 | model_name='ratingcategorychoicetranslation', 42 | name='master', 43 | field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='review.RatingCategoryChoice'), 44 | ), 45 | migrations.AlterField( 46 | model_name='ratingcategorytranslation', 47 | name='master', 48 | field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='review.RatingCategory'), 49 | ), 50 | ] 51 | -------------------------------------------------------------------------------- /review/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitlabstudio/django-review/70d4b5c8d52d9a5615e5d0f5c7f147e15573c566/review/migrations/__init__.py -------------------------------------------------------------------------------- /review/models.py: -------------------------------------------------------------------------------- 1 | """Just an empty models file to let the testrunner recognize this as app.""" 2 | from django.conf import settings 3 | from django.contrib.contenttypes import fields 4 | from django.contrib.contenttypes.models import ContentType 5 | from django.db import models 6 | from django.utils import timezone 7 | from django.utils.encoding import python_2_unicode_compatible 8 | from django.utils.translation import ugettext, ugettext_lazy as _ 9 | 10 | from hvad.models import TranslatableModel, TranslatedFields 11 | 12 | DEFAULT_CHOICES = ( 13 | ('5', '5'), 14 | ('4', '4'), 15 | ('3', '3'), 16 | ('2', '2'), 17 | ('1', '1'), 18 | ) 19 | 20 | 21 | @python_2_unicode_compatible 22 | class Review(models.Model): 23 | """ 24 | Represents a user review, which includes free text and images. 25 | 26 | :reviewed_item: Object, which is reviewed. 27 | :user (optional): User, which posted the rating. 28 | :content (optional): Running text. 29 | :images (optional): Review-related images. 30 | :language (optional): Language shortcut to filter reviews. 31 | :creation_date: The date and time, this review was created. 32 | :average_rating: Should always be calculated and updated when the object is 33 | saved. This is for improving performance and reducing db queries when 34 | calculating ratings for reviewed items. Currently it gets updated at the 35 | end of the save method of the ``ReviewForm``. This means that when you 36 | manually save a Review via the Django admin, this field will not be 37 | updated. 38 | :extra_item: Optional object, which should be attached to the review. 39 | 40 | """ 41 | # GFK 'reviewed_item' 42 | content_type = models.ForeignKey(ContentType) 43 | object_id = models.PositiveIntegerField() 44 | reviewed_item = fields.GenericForeignKey('content_type', 'object_id') 45 | 46 | user = models.ForeignKey( 47 | getattr(settings, 'AUTH_USER_MODEL', 'auth.User'), 48 | verbose_name=_('User'), 49 | blank=True, null=True, 50 | ) 51 | 52 | content = models.TextField( 53 | max_length=1024, 54 | verbose_name=_('Content'), 55 | blank=True, 56 | ) 57 | 58 | images = fields.GenericRelation( 59 | 'user_media.UserMediaImage', 60 | ) 61 | 62 | language = models.CharField( 63 | max_length=5, 64 | verbose_name=_('Language'), 65 | blank=True, 66 | ) 67 | 68 | creation_date = models.DateTimeField( 69 | auto_now_add=True, 70 | verbose_name=_('Creation date'), 71 | ) 72 | 73 | average_rating = models.FloatField( 74 | verbose_name=_('Average rating'), 75 | default=0, 76 | ) 77 | 78 | # GFK 'extra_item' 79 | extra_content_type = models.ForeignKey( 80 | ContentType, 81 | related_name='reviews_attached', 82 | null=True, blank=True, 83 | ) 84 | extra_object_id = models.PositiveIntegerField(null=True, blank=True) 85 | extra_item = fields.GenericForeignKey( 86 | 'extra_content_type', 'extra_object_id') 87 | 88 | class Meta: 89 | ordering = ['-creation_date'] 90 | 91 | def __str__(self): 92 | return '{0} - {1}'.format(self.reviewed_item, self.get_user()) 93 | 94 | # TODO: Add magic to get ReviewExtraInfo content objects here 95 | 96 | def get_user(self): 97 | """Returns the user who wrote this review or ``Anonymous``.""" 98 | if self.user: 99 | return self.user.email 100 | return ugettext('Anonymous') 101 | 102 | def get_averages(self, max_value=None): 103 | """ 104 | Centralized average calculation. Returns category averages and total 105 | average. 106 | 107 | :param max_value: By default the app is set to a rating from 1 to 5. 108 | So if nothing is changed, we can just calculate the average of all 109 | rating values and be good. We then have an average that is between 1 110 | and 5 as well. 111 | BUT if we have custom choices, we could end up having one category 112 | with a range of 1 to 10 and one category with 1 to 5. The result then 113 | must be abstracted to fit into the given range set by max_value. 114 | 115 | This can also be used to calculate percentages by setting max_value 116 | to 100. 117 | 118 | """ 119 | max_rating_value = 0 120 | category_maximums = {} 121 | category_averages = {} 122 | categories = RatingCategory.objects.filter(counts_for_average=True, 123 | rating__review=self) 124 | # find the highest rating possible across all categories 125 | for category in categories: 126 | category_max = category.get_rating_max_from_choices() 127 | category_maximums.update({category: category_max}) 128 | if max_value is not None: 129 | max_rating_value = max_value 130 | else: 131 | if category_max > max_rating_value: 132 | max_rating_value = category_max 133 | # calculate the average of every distinct category, normalized to the 134 | # recently found max 135 | for category in categories: 136 | category_average = None 137 | ratings = Rating.objects.filter( 138 | review=self, 139 | category=category, value__isnull=False).exclude(value='') 140 | category_max = category_maximums[category] 141 | for rating in ratings: 142 | if category_average is None: 143 | category_average = float(rating.value) 144 | else: 145 | category_average += float(rating.value) 146 | 147 | if category_average is not None: 148 | category_average *= float(max_rating_value) / float( 149 | category_max) 150 | category_averages[category] = ( 151 | category_average / ratings.count()) 152 | 153 | # calculate the total average of all categories 154 | total_average = 0 155 | for category, category_average in category_averages.items(): 156 | total_average += category_average 157 | if not len(category_averages): 158 | return (False, False) 159 | total_average /= len(category_averages) 160 | 161 | return total_average, category_averages 162 | 163 | def get_average_rating(self, max_value=None): 164 | """ 165 | Returns the average rating for all categories of this review. 166 | 167 | A shortcut for get_averages. Look there for more details. 168 | 169 | """ 170 | total_average, category_averages = self.get_averages( 171 | max_value=max_value) 172 | return total_average 173 | 174 | def get_category_averages(self, max_value=None): 175 | """ 176 | Returns the average ratings for every category of this review. 177 | 178 | A shortcut for get_averages. Look there for more details. 179 | 180 | """ 181 | total_average, category_averages = self.get_averages( 182 | max_value=max_value) 183 | return category_averages 184 | 185 | def is_editable(self): 186 | """ 187 | Returns True, if the time period to update this review hasn't ended 188 | yet. 189 | 190 | If the period setting has not been set, it always return True. This 191 | is the general case. If the user has used this setting to define an 192 | update period it returns False, if this period has expired. 193 | 194 | """ 195 | if getattr(settings, 'REVIEW_UPDATE_PERIOD', False): 196 | period_end = self.creation_date + timezone.timedelta( 197 | seconds=getattr(settings, 'REVIEW_UPDATE_PERIOD') * 60) 198 | if timezone.now() > period_end: 199 | return False 200 | return True 201 | 202 | 203 | @python_2_unicode_compatible 204 | class ReviewExtraInfo(models.Model): 205 | """ 206 | Model to add any extra information to a review. 207 | 208 | This can be useful if you need to save more information about a reviewer 209 | than just the User instance. Let's say you are building a site for theme 210 | park reviews and you want to allow the user to select the weather 211 | conditions for the day of his visit (which will surely influence his 212 | review). This model would allow you to tie any model of your app to a 213 | review. 214 | 215 | :type: Callable type of the extra info. This should be unique per review. 216 | We will soon add a hack to the Review model which allows you to get the 217 | content_object of this instance from a review instance (i.e. by calling 218 | ``my_review.weather_conditions.name``). So for this example you would 219 | set the type to ``weather_conditions``. 220 | :review: Related review. 221 | :content_object: The related object that stores this extra information. 222 | 223 | """ 224 | type = models.CharField( 225 | max_length=256, 226 | verbose_name=_('Type'), 227 | ) 228 | 229 | review = models.ForeignKey( 230 | 'review.Review', 231 | verbose_name=_('Review'), 232 | ) 233 | 234 | # GFK 'content_object' 235 | content_type = models.ForeignKey(ContentType) 236 | object_id = models.PositiveIntegerField() 237 | content_object = fields.GenericForeignKey('content_type', 'object_id') 238 | 239 | class Meta: 240 | ordering = ['type'] 241 | 242 | def __str__(self): 243 | return '{0} - {1}'.format(self.review, self.type) 244 | 245 | 246 | @python_2_unicode_compatible 247 | class RatingCategory(TranslatableModel): 248 | """ 249 | Represents a rating category. 250 | 251 | If your reviews are just text based, you don't have to use this. 252 | 253 | This can be useful if you want to allow users to rate one or more 254 | categories, like ``Food``, ``Room service``, ``Cleansines`` and so on. 255 | 256 | :identifier: Optional identifier. 257 | :name: Name of the category. Also used as label for the category form. 258 | :question: If you want to render a more explicit question in addition to 259 | the name, use this field. It is added to the form fields as help text. 260 | :counts_for_average: If True, the ratings of this category will be used to 261 | calculate the average rating. Default is True. 262 | 263 | """ 264 | identifier = models.SlugField( 265 | max_length=32, 266 | verbose_name=_('Identifier'), 267 | blank=True, 268 | ) 269 | 270 | counts_for_average = models.BooleanField( 271 | verbose_name=_('Counts for average rating'), 272 | default=True, 273 | ) 274 | 275 | translations = TranslatedFields( 276 | name=models.CharField(max_length=256), 277 | question=models.CharField(max_length=512, blank=True, null=True), 278 | ) 279 | 280 | def __str__(self): 281 | return self.lazy_translation_getter('name', 'Untranslated') 282 | 283 | @property 284 | def required(self): 285 | """Returns False, if the choices include a None value.""" 286 | if not hasattr(self, '_required'): 287 | # get_choices sets _required 288 | self.get_choices() 289 | return self._required 290 | 291 | def get_choices(self): 292 | """Returns the tuple of choices for this category.""" 293 | choices = () 294 | self._required = True 295 | for choice in self.choices.all(): 296 | if choice.value is None or choice.value == '': 297 | self._required = False 298 | choices += (choice.value, choice.label), 299 | if not choices: 300 | return DEFAULT_CHOICES 301 | return choices 302 | 303 | def get_rating_max_from_choices(self): 304 | """Returns the maximun value a rating can have in this catgory.""" 305 | return int(list(self.get_choices())[0][0]) 306 | 307 | 308 | @python_2_unicode_compatible 309 | class RatingCategoryChoice(TranslatableModel): 310 | """ 311 | Defines an optional choice for a `RatingCategory`. 312 | 313 | If `RatingChoice` exists, the choices will not be loaded from the settings. 314 | 315 | :label: The label that is displayed for this choice. 316 | :ratingcategory: The `RatingCategory` this choice belongs to. 317 | :value: The value that this choice has. If a `RatingChoice` with value=None 318 | is created and chosen by the user, this category is not taken into 319 | account when the average is calculated. 320 | 321 | """ 322 | ratingcategory = models.ForeignKey( 323 | RatingCategory, 324 | verbose_name=_('Rating category'), 325 | related_name='choices', 326 | ) 327 | 328 | value = models.CharField( 329 | verbose_name=_('Value'), 330 | max_length=20, 331 | blank=True, null=True, 332 | ) 333 | 334 | translations = TranslatedFields( 335 | label=models.CharField( 336 | verbose_name=_('Label'), 337 | max_length=128, 338 | ), 339 | ) 340 | 341 | def __str__(self): 342 | return self.lazy_translation_getter('label', 343 | self.ratingcategory.identifier) 344 | 345 | class Meta: 346 | ordering = ('-value', ) 347 | 348 | 349 | @python_2_unicode_compatible 350 | class Rating(models.Model): 351 | """ 352 | Represents a rating for one rating category. 353 | 354 | :rating: Rating value. 355 | :review: The review the rating belongs to. 356 | :category: The rating category the rating belongs to. 357 | 358 | """ 359 | rating_choices = DEFAULT_CHOICES 360 | 361 | value = models.CharField( 362 | max_length=20, 363 | verbose_name=_('Value'), 364 | choices=getattr(settings, 'REVIEW_RATING_CHOICES', rating_choices), 365 | blank=True, null=True, 366 | ) 367 | 368 | review = models.ForeignKey( 369 | 'review.Review', 370 | verbose_name=_('Review'), 371 | related_name='ratings', 372 | ) 373 | 374 | category = models.ForeignKey( 375 | 'review.RatingCategory', 376 | verbose_name=_('Category'), 377 | ) 378 | 379 | class Meta: 380 | ordering = ['category', 'review'] 381 | 382 | def __str__(self): 383 | return '{0}/{1} - {2}'.format(self.category, self.review, self.value) 384 | -------------------------------------------------------------------------------- /review/south_migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | # -*- coding: utf-8 -*- 3 | import datetime 4 | from south.db import db 5 | from south.v2 import SchemaMigration 6 | from django.db import models 7 | 8 | from ..compat import USER_MODEL 9 | 10 | 11 | class Migration(SchemaMigration): 12 | 13 | def forwards(self, orm): 14 | # Adding model 'Review' 15 | db.create_table(u'review_review', ( 16 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 17 | ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])), 18 | ('object_id', self.gf('django.db.models.fields.PositiveIntegerField')()), 19 | ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm[USER_MODEL['orm_label']], null=True, blank=True)), 20 | ('content', self.gf('django.db.models.fields.TextField')(max_length=1024, blank=True)), 21 | ('language', self.gf('django.db.models.fields.CharField')(max_length=5, blank=True)), 22 | ('creation_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), 23 | )) 24 | db.send_create_signal(u'review', ['Review']) 25 | 26 | # Adding model 'ReviewExtraInfo' 27 | db.create_table(u'review_reviewextrainfo', ( 28 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 29 | ('type', self.gf('django.db.models.fields.CharField')(max_length=256)), 30 | ('review', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['review.Review'])), 31 | ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])), 32 | ('object_id', self.gf('django.db.models.fields.PositiveIntegerField')()), 33 | )) 34 | db.send_create_signal(u'review', ['ReviewExtraInfo']) 35 | 36 | # Adding model 'RatingCategory' 37 | db.create_table(u'review_ratingcategory', ( 38 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 39 | )) 40 | db.send_create_signal(u'review', ['RatingCategory']) 41 | 42 | # Adding model 'RatingCategoryTranslation' 43 | db.create_table(u'review_ratingcategorytranslation', ( 44 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 45 | ('name', self.gf('django.db.models.fields.CharField')(max_length=256)), 46 | ('category', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['review.RatingCategory'])), 47 | ('language', self.gf('django.db.models.fields.CharField')(max_length=2)), 48 | )) 49 | db.send_create_signal(u'review', ['RatingCategoryTranslation']) 50 | 51 | # Adding model 'Rating' 52 | db.create_table(u'review_rating', ( 53 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 54 | ('value', self.gf('django.db.models.fields.CharField')(max_length=20)), 55 | ('review', self.gf('django.db.models.fields.related.ForeignKey')(related_name='ratings', to=orm['review.Review'])), 56 | ('category', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['review.RatingCategory'])), 57 | )) 58 | db.send_create_signal(u'review', ['Rating']) 59 | 60 | 61 | def backwards(self, orm): 62 | # Deleting model 'Review' 63 | db.delete_table(u'review_review') 64 | 65 | # Deleting model 'ReviewExtraInfo' 66 | db.delete_table(u'review_reviewextrainfo') 67 | 68 | # Deleting model 'RatingCategory' 69 | db.delete_table(u'review_ratingcategory') 70 | 71 | # Deleting model 'RatingCategoryTranslation' 72 | db.delete_table(u'review_ratingcategorytranslation') 73 | 74 | # Deleting model 'Rating' 75 | db.delete_table(u'review_rating') 76 | 77 | 78 | models = { 79 | u'auth.group': { 80 | 'Meta': {'object_name': 'Group'}, 81 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 82 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 83 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 84 | }, 85 | u'auth.permission': { 86 | 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, 87 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 88 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 89 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 90 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 91 | }, 92 | USER_MODEL['model_label']: { 93 | 'Meta': {'object_name': USER_MODEL['object_name']}, 94 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 95 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 96 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 97 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 98 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 99 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 100 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 101 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 102 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 103 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 104 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 105 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 106 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 107 | }, 108 | u'contenttypes.contenttype': { 109 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 110 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 111 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 112 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 113 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 114 | }, 115 | u'review.rating': { 116 | 'Meta': {'ordering': "['category', 'review']", 'object_name': 'Rating'}, 117 | 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.RatingCategory']"}), 118 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 119 | 'review': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ratings'", 'to': u"orm['review.Review']"}), 120 | 'value': ('django.db.models.fields.CharField', [], {'max_length': '20'}) 121 | }, 122 | u'review.ratingcategory': { 123 | 'Meta': {'object_name': 'RatingCategory'}, 124 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) 125 | }, 126 | u'review.ratingcategorytranslation': { 127 | 'Meta': {'object_name': 'RatingCategoryTranslation'}, 128 | 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.RatingCategory']"}), 129 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 130 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '2'}), 131 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}) 132 | }, 133 | u'review.review': { 134 | 'Meta': {'ordering': "['-creation_date']", 'object_name': 'Review'}, 135 | 'content': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'blank': 'True'}), 136 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 137 | 'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 138 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 139 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '5', 'blank': 'True'}), 140 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 141 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['%s']" % USER_MODEL['orm_label'], 'null': 'True', 'blank': 'True'}) 142 | }, 143 | u'review.reviewextrainfo': { 144 | 'Meta': {'ordering': "['type']", 'object_name': 'ReviewExtraInfo'}, 145 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 146 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 147 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 148 | 'review': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.Review']"}), 149 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '256'}) 150 | }, 151 | u'user_media.usermediaimage': { 152 | 'Meta': {'object_name': 'UserMediaImage'}, 153 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), 154 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 155 | 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), 156 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 157 | 'position': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 158 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['%s']" % USER_MODEL['orm_label']}) 159 | } 160 | } 161 | 162 | complete_apps = ['review'] -------------------------------------------------------------------------------- /review/south_migrations/0002_auto__add_field_review_average_rating.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | # -*- coding: utf-8 -*- 3 | import datetime 4 | from south.db import db 5 | from south.v2 import SchemaMigration 6 | from django.db import models 7 | 8 | from ..compat import USER_MODEL 9 | 10 | 11 | class Migration(SchemaMigration): 12 | 13 | def forwards(self, orm): 14 | # Adding field 'Review.average_rating' 15 | db.add_column(u'review_review', 'average_rating', 16 | self.gf('django.db.models.fields.FloatField')(default=0), 17 | keep_default=False) 18 | 19 | 20 | def backwards(self, orm): 21 | # Deleting field 'Review.average_rating' 22 | db.delete_column(u'review_review', 'average_rating') 23 | 24 | 25 | models = { 26 | u'auth.group': { 27 | 'Meta': {'object_name': 'Group'}, 28 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 29 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 30 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 31 | }, 32 | u'auth.permission': { 33 | 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, 34 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 35 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 36 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 37 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 38 | }, 39 | USER_MODEL['model_label']: { 40 | 'Meta': {'object_name': USER_MODEL['object_name']}, 41 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 42 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 43 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 44 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 45 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 46 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 47 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 48 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 49 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 50 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 51 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 52 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 53 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 54 | }, 55 | u'contenttypes.contenttype': { 56 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 57 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 58 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 59 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 60 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 61 | }, 62 | u'review.rating': { 63 | 'Meta': {'ordering': "['category', 'review']", 'object_name': 'Rating'}, 64 | 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.RatingCategory']"}), 65 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 66 | 'review': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ratings'", 'to': u"orm['review.Review']"}), 67 | 'value': ('django.db.models.fields.CharField', [], {'max_length': '20'}) 68 | }, 69 | u'review.ratingcategory': { 70 | 'Meta': {'object_name': 'RatingCategory'}, 71 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) 72 | }, 73 | u'review.ratingcategorytranslation': { 74 | 'Meta': {'object_name': 'RatingCategoryTranslation'}, 75 | 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.RatingCategory']"}), 76 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 77 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '2'}), 78 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}) 79 | }, 80 | u'review.review': { 81 | 'Meta': {'ordering': "['-creation_date']", 'object_name': 'Review'}, 82 | 'average_rating': ('django.db.models.fields.FloatField', [], {'default': '0'}), 83 | 'content': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'blank': 'True'}), 84 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 85 | 'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 86 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 87 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '5', 'blank': 'True'}), 88 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 89 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['%s']" % USER_MODEL['orm_label'], 'null': 'True', 'blank': 'True'}) 90 | }, 91 | u'review.reviewextrainfo': { 92 | 'Meta': {'ordering': "['type']", 'object_name': 'ReviewExtraInfo'}, 93 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 94 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 95 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 96 | 'review': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.Review']"}), 97 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '256'}) 98 | }, 99 | u'user_media.usermediaimage': { 100 | 'Meta': {'object_name': 'UserMediaImage'}, 101 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), 102 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 103 | 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), 104 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 105 | 'position': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 106 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['%s']" % USER_MODEL['orm_label']}) 107 | } 108 | } 109 | 110 | complete_apps = ['review'] 111 | -------------------------------------------------------------------------------- /review/south_migrations/0003_auto__add_field_ratingcategory_identifier.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | # -*- coding: utf-8 -*- 3 | from south.utils import datetime_utils as datetime 4 | from south.db import db 5 | from south.v2 import SchemaMigration 6 | from django.db import models 7 | 8 | from ..compat import USER_MODEL 9 | 10 | 11 | class Migration(SchemaMigration): 12 | 13 | def forwards(self, orm): 14 | # Adding field 'RatingCategory.identifier' 15 | db.add_column(u'review_ratingcategory', 'identifier', 16 | self.gf('django.db.models.fields.SlugField')(default='', max_length=32, blank=True), 17 | keep_default=False) 18 | 19 | 20 | def backwards(self, orm): 21 | # Deleting field 'RatingCategory.identifier' 22 | db.delete_column(u'review_ratingcategory', 'identifier') 23 | 24 | 25 | models = { 26 | u'auth.group': { 27 | 'Meta': {'object_name': 'Group'}, 28 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 29 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 30 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 31 | }, 32 | u'auth.permission': { 33 | 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, 34 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 35 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 36 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 37 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 38 | }, 39 | USER_MODEL['model_label']: { 40 | 'Meta': {'object_name': USER_MODEL['object_name']}, 41 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 42 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 43 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 44 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 45 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 46 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 47 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 48 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 49 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 50 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 51 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 52 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 53 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 54 | }, 55 | u'contenttypes.contenttype': { 56 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 57 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 58 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 59 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 60 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 61 | }, 62 | u'review.rating': { 63 | 'Meta': {'ordering': "['category', 'review']", 'object_name': 'Rating'}, 64 | 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.RatingCategory']"}), 65 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 66 | 'review': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ratings'", 'to': u"orm['review.Review']"}), 67 | 'value': ('django.db.models.fields.CharField', [], {'max_length': '20'}) 68 | }, 69 | u'review.ratingcategory': { 70 | 'Meta': {'object_name': 'RatingCategory'}, 71 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 72 | 'identifier': ('django.db.models.fields.SlugField', [], {'max_length': '32', 'blank': 'True'}) 73 | }, 74 | u'review.ratingcategorytranslation': { 75 | 'Meta': {'object_name': 'RatingCategoryTranslation'}, 76 | 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.RatingCategory']"}), 77 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 78 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '2'}), 79 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}) 80 | }, 81 | u'review.review': { 82 | 'Meta': {'ordering': "['-creation_date']", 'object_name': 'Review'}, 83 | 'average_rating': ('django.db.models.fields.FloatField', [], {'default': '0'}), 84 | 'content': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'blank': 'True'}), 85 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 86 | 'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 87 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 88 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '5', 'blank': 'True'}), 89 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 90 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['%s']" % USER_MODEL['orm_label'], 'null': 'True', 'blank': 'True'}) 91 | }, 92 | u'review.reviewextrainfo': { 93 | 'Meta': {'ordering': "['type']", 'object_name': 'ReviewExtraInfo'}, 94 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 95 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 96 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 97 | 'review': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.Review']"}), 98 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '256'}) 99 | }, 100 | u'user_media.usermediaimage': { 101 | 'Meta': {'object_name': 'UserMediaImage'}, 102 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), 103 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 104 | 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), 105 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 106 | 'position': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 107 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['%s']" % USER_MODEL['orm_label']}) 108 | } 109 | } 110 | 111 | complete_apps = ['review'] -------------------------------------------------------------------------------- /review/south_migrations/0004_auto__del_ratingcategory__del_ratingcategorytranslation__del_field_rat.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | # -*- coding: utf-8 -*- 3 | from south.utils import datetime_utils as datetime 4 | from south.db import db 5 | from south.v2 import SchemaMigration 6 | from django.db import models 7 | 8 | from ..compat import USER_MODEL 9 | 10 | 11 | class Migration(SchemaMigration): 12 | 13 | def forwards(self, orm): 14 | # Deleting model 'RatingCategory' 15 | db.delete_table(u'review_ratingcategory') 16 | 17 | # Deleting model 'RatingCategoryTranslation' 18 | db.delete_table(u'review_ratingcategorytranslation') 19 | 20 | # Deleting field 'Rating.category' 21 | db.delete_column(u'review_rating', 'category_id') 22 | 23 | 24 | def backwards(self, orm): 25 | # Adding model 'RatingCategory' 26 | db.create_table(u'review_ratingcategory', ( 27 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 28 | ('identifier', self.gf('django.db.models.fields.SlugField')(max_length=32, blank=True)), 29 | )) 30 | db.send_create_signal(u'review', ['RatingCategory']) 31 | 32 | # Adding model 'RatingCategoryTranslation' 33 | db.create_table(u'review_ratingcategorytranslation', ( 34 | ('category', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['review.RatingCategory'])), 35 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 36 | ('language', self.gf('django.db.models.fields.CharField')(max_length=2)), 37 | ('name', self.gf('django.db.models.fields.CharField')(max_length=256)), 38 | )) 39 | db.send_create_signal(u'review', ['RatingCategoryTranslation']) 40 | 41 | 42 | # User chose to not deal with backwards NULL issues for 'Rating.category' 43 | raise RuntimeError("Cannot reverse this migration. 'Rating.category' and its values cannot be restored.") 44 | 45 | # The following code is provided here to aid in writing a correct migration # Adding field 'Rating.category' 46 | db.add_column(u'review_rating', 'category', 47 | self.gf('django.db.models.fields.related.ForeignKey')(to=orm['review.RatingCategory']), 48 | keep_default=False) 49 | 50 | 51 | models = { 52 | u'auth.group': { 53 | 'Meta': {'object_name': 'Group'}, 54 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 55 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 56 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 57 | }, 58 | u'auth.permission': { 59 | 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, 60 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 61 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 62 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 63 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 64 | }, 65 | USER_MODEL['model_label']: { 66 | 'Meta': {'object_name': USER_MODEL['object_name']}, 67 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 68 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 69 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 70 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 71 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 72 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 73 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 74 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 75 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 76 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 77 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 78 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 79 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 80 | }, 81 | u'contenttypes.contenttype': { 82 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 83 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 84 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 85 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 86 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 87 | }, 88 | u'review.rating': { 89 | 'Meta': {'ordering': "['review']", 'object_name': 'Rating'}, 90 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 91 | 'review': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ratings'", 'to': u"orm['review.Review']"}), 92 | 'value': ('django.db.models.fields.CharField', [], {'max_length': '20'}) 93 | }, 94 | u'review.review': { 95 | 'Meta': {'ordering': "['-creation_date']", 'object_name': 'Review'}, 96 | 'average_rating': ('django.db.models.fields.FloatField', [], {'default': '0'}), 97 | 'content': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'blank': 'True'}), 98 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 99 | 'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 100 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 101 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '5', 'blank': 'True'}), 102 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 103 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['%s']" % USER_MODEL['orm_label'], 'null': 'True', 'blank': 'True'}) 104 | }, 105 | u'review.reviewextrainfo': { 106 | 'Meta': {'ordering': "['type']", 'object_name': 'ReviewExtraInfo'}, 107 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 108 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 109 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 110 | 'review': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.Review']"}), 111 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '256'}) 112 | }, 113 | u'user_media.usermediaimage': { 114 | 'Meta': {'object_name': 'UserMediaImage'}, 115 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), 116 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 117 | 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), 118 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 119 | 'position': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 120 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['%s']" % USER_MODEL['orm_label']}) 121 | } 122 | } 123 | 124 | complete_apps = ['review'] -------------------------------------------------------------------------------- /review/south_migrations/0005_auto__add_ratingcategory__add_ratingcategorytranslation__add_unique_ra.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | # -*- coding: utf-8 -*- 3 | from south.utils import datetime_utils as datetime 4 | from south.db import db 5 | from south.v2 import SchemaMigration 6 | from django.db import models 7 | 8 | from ..compat import USER_MODEL 9 | 10 | 11 | class Migration(SchemaMigration): 12 | 13 | def forwards(self, orm): 14 | # Adding model 'RatingCategory' 15 | db.create_table(u'review_ratingcategory', ( 16 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 17 | ('identifier', self.gf('django.db.models.fields.SlugField')(max_length=32, blank=True)), 18 | )) 19 | db.send_create_signal(u'review', ['RatingCategory']) 20 | 21 | # Adding model 'RatingCategoryTranslation' 22 | db.create_table(u'review_ratingcategory_translation', ( 23 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 24 | ('name', self.gf('django.db.models.fields.CharField')(max_length=256)), 25 | ('language_code', self.gf('django.db.models.fields.CharField')(max_length=15, db_index=True)), 26 | ('master', self.gf('django.db.models.fields.related.ForeignKey')(related_name='translations', null=True, to=orm['review.RatingCategory'])), 27 | )) 28 | db.send_create_signal(u'review', ['RatingCategoryTranslation']) 29 | 30 | # Adding unique constraint on 'RatingCategoryTranslation', fields ['language_code', 'master'] 31 | db.create_unique(u'review_ratingcategory_translation', ['language_code', 'master_id']) 32 | 33 | # Adding field 'Rating.category' 34 | db.add_column(u'review_rating', 'category', 35 | self.gf('django.db.models.fields.related.ForeignKey')(default=1, to=orm['review.RatingCategory']), 36 | keep_default=False) 37 | 38 | 39 | def backwards(self, orm): 40 | # Removing unique constraint on 'RatingCategoryTranslation', fields ['language_code', 'master'] 41 | db.delete_unique(u'review_ratingcategory_translation', ['language_code', 'master_id']) 42 | 43 | # Deleting model 'RatingCategory' 44 | db.delete_table(u'review_ratingcategory') 45 | 46 | # Deleting model 'RatingCategoryTranslation' 47 | db.delete_table(u'review_ratingcategory_translation') 48 | 49 | # Deleting field 'Rating.category' 50 | db.delete_column(u'review_rating', 'category_id') 51 | 52 | 53 | models = { 54 | u'auth.group': { 55 | 'Meta': {'object_name': 'Group'}, 56 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 57 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 58 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 59 | }, 60 | u'auth.permission': { 61 | 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, 62 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 63 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 64 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 65 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 66 | }, 67 | USER_MODEL['model_label']: { 68 | 'Meta': {'object_name': USER_MODEL['object_name']}, 69 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 70 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 71 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 72 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 73 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 74 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 75 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 76 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 77 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 78 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 79 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 80 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 81 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 82 | }, 83 | u'contenttypes.contenttype': { 84 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 85 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 86 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 87 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 88 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 89 | }, 90 | u'review.rating': { 91 | 'Meta': {'ordering': "['category', 'review']", 'object_name': 'Rating'}, 92 | 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.RatingCategory']"}), 93 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 94 | 'review': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ratings'", 'to': u"orm['review.Review']"}), 95 | 'value': ('django.db.models.fields.CharField', [], {'max_length': '20'}) 96 | }, 97 | u'review.ratingcategory': { 98 | 'Meta': {'object_name': 'RatingCategory'}, 99 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 100 | 'identifier': ('django.db.models.fields.SlugField', [], {'max_length': '32', 'blank': 'True'}) 101 | }, 102 | u'review.ratingcategorytranslation': { 103 | 'Meta': {'unique_together': "[('language_code', 'master')]", 'object_name': 'RatingCategoryTranslation', 'db_table': "u'review_ratingcategory_translation'"}, 104 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 105 | 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), 106 | 'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'translations'", 'null': 'True', 'to': u"orm['review.RatingCategory']"}), 107 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}) 108 | }, 109 | u'review.review': { 110 | 'Meta': {'ordering': "['-creation_date']", 'object_name': 'Review'}, 111 | 'average_rating': ('django.db.models.fields.FloatField', [], {'default': '0'}), 112 | 'content': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'blank': 'True'}), 113 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 114 | 'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 115 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 116 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '5', 'blank': 'True'}), 117 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 118 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['%s']" % USER_MODEL['orm_label'], 'null': 'True', 'blank': 'True'}) 119 | }, 120 | u'review.reviewextrainfo': { 121 | 'Meta': {'ordering': "['type']", 'object_name': 'ReviewExtraInfo'}, 122 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 123 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 124 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 125 | 'review': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.Review']"}), 126 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '256'}) 127 | }, 128 | u'user_media.usermediaimage': { 129 | 'Meta': {'object_name': 'UserMediaImage'}, 130 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), 131 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 132 | 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), 133 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 134 | 'position': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 135 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['%s']" % USER_MODEL['orm_label']}) 136 | } 137 | } 138 | 139 | complete_apps = ['review'] -------------------------------------------------------------------------------- /review/south_migrations/0006_auto__add_field_review_extra_content_type__add_field_review_extra_obje.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | # -*- coding: utf-8 -*- 3 | from south.utils import datetime_utils as datetime 4 | from south.db import db 5 | from south.v2 import SchemaMigration 6 | from django.db import models 7 | 8 | from ..compat import USER_MODEL 9 | 10 | 11 | class Migration(SchemaMigration): 12 | 13 | def forwards(self, orm): 14 | # Adding field 'Review.extra_content_type' 15 | db.add_column(u'review_review', 'extra_content_type', 16 | self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='reviews_attached', null=True, to=orm['contenttypes.ContentType']), 17 | keep_default=False) 18 | 19 | # Adding field 'Review.extra_object_id' 20 | db.add_column(u'review_review', 'extra_object_id', 21 | self.gf('django.db.models.fields.PositiveIntegerField')(null=True, blank=True), 22 | keep_default=False) 23 | 24 | 25 | def backwards(self, orm): 26 | # Deleting field 'Review.extra_content_type' 27 | db.delete_column(u'review_review', 'extra_content_type_id') 28 | 29 | # Deleting field 'Review.extra_object_id' 30 | db.delete_column(u'review_review', 'extra_object_id') 31 | 32 | 33 | models = { 34 | u'auth.group': { 35 | 'Meta': {'object_name': 'Group'}, 36 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 37 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 38 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 39 | }, 40 | u'auth.permission': { 41 | 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, 42 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 43 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 44 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 45 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 46 | }, 47 | USER_MODEL['model_label']: { 48 | 'Meta': {'object_name': USER_MODEL['object_name']}, 49 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 50 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 51 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 52 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 53 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 54 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 55 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 56 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 57 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 58 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 59 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 60 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 61 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 62 | }, 63 | u'contenttypes.contenttype': { 64 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 65 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 66 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 67 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 68 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 69 | }, 70 | u'review.rating': { 71 | 'Meta': {'ordering': "['category', 'review']", 'object_name': 'Rating'}, 72 | 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.RatingCategory']"}), 73 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 74 | 'review': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ratings'", 'to': u"orm['review.Review']"}), 75 | 'value': ('django.db.models.fields.CharField', [], {'max_length': '20'}) 76 | }, 77 | u'review.ratingcategory': { 78 | 'Meta': {'object_name': 'RatingCategory'}, 79 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 80 | 'identifier': ('django.db.models.fields.SlugField', [], {'max_length': '32', 'blank': 'True'}) 81 | }, 82 | u'review.ratingcategorytranslation': { 83 | 'Meta': {'unique_together': "[('language_code', 'master')]", 'object_name': 'RatingCategoryTranslation', 'db_table': "u'review_ratingcategory_translation'"}, 84 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 85 | 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), 86 | 'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'translations'", 'null': 'True', 'to': u"orm['review.RatingCategory']"}), 87 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}) 88 | }, 89 | u'review.review': { 90 | 'Meta': {'ordering': "['-creation_date']", 'object_name': 'Review'}, 91 | 'average_rating': ('django.db.models.fields.FloatField', [], {'default': '0'}), 92 | 'content': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'blank': 'True'}), 93 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 94 | 'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 95 | 'extra_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'reviews_attached'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}), 96 | 'extra_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 97 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 98 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '5', 'blank': 'True'}), 99 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 100 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['%s']" % USER_MODEL['orm_label'], 'null': 'True', 'blank': 'True'}) 101 | }, 102 | u'review.reviewextrainfo': { 103 | 'Meta': {'ordering': "['type']", 'object_name': 'ReviewExtraInfo'}, 104 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 105 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 106 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 107 | 'review': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.Review']"}), 108 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '256'}) 109 | }, 110 | u'user_media.usermediaimage': { 111 | 'Meta': {'object_name': 'UserMediaImage'}, 112 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), 113 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 114 | 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), 115 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 116 | 'position': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 117 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['%s']" % USER_MODEL['orm_label']}) 118 | } 119 | } 120 | 121 | complete_apps = ['review'] -------------------------------------------------------------------------------- /review/south_migrations/0007_auto__add_ratingcategorychoicetranslation__add_unique_ratingcategorych.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | # -*- coding: utf-8 -*- 3 | import datetime 4 | from south.db import db 5 | from south.v2 import SchemaMigration 6 | from django.db import models 7 | 8 | from ..compat import USER_MODEL 9 | 10 | 11 | class Migration(SchemaMigration): 12 | 13 | def forwards(self, orm): 14 | # Adding model 'RatingCategoryChoiceTranslation' 15 | db.create_table(u'review_ratingcategorychoice_translation', ( 16 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 17 | ('label', self.gf('django.db.models.fields.CharField')(max_length=128)), 18 | ('language_code', self.gf('django.db.models.fields.CharField')(max_length=15, db_index=True)), 19 | ('master', self.gf('django.db.models.fields.related.ForeignKey')(related_name='translations', null=True, to=orm['review.RatingCategoryChoice'])), 20 | )) 21 | db.send_create_signal(u'review', ['RatingCategoryChoiceTranslation']) 22 | 23 | # Adding unique constraint on 'RatingCategoryChoiceTranslation', fields ['language_code', 'master'] 24 | db.create_unique(u'review_ratingcategorychoice_translation', ['language_code', 'master_id']) 25 | 26 | # Adding model 'RatingCategoryChoice' 27 | db.create_table(u'review_ratingcategorychoice', ( 28 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 29 | ('ratingcategory', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['review.RatingCategory'])), 30 | ('value', self.gf('django.db.models.fields.PositiveIntegerField')(null=True, blank=True)), 31 | )) 32 | db.send_create_signal(u'review', ['RatingCategoryChoice']) 33 | 34 | # Adding field 'RatingCategory.counts_for_average' 35 | db.add_column(u'review_ratingcategory', 'counts_for_average', 36 | self.gf('django.db.models.fields.BooleanField')(default=True), 37 | keep_default=False) 38 | 39 | 40 | def backwards(self, orm): 41 | # Removing unique constraint on 'RatingCategoryChoiceTranslation', fields ['language_code', 'master'] 42 | db.delete_unique(u'review_ratingcategorychoice_translation', ['language_code', 'master_id']) 43 | 44 | # Deleting model 'RatingCategoryChoiceTranslation' 45 | db.delete_table(u'review_ratingcategorychoice_translation') 46 | 47 | # Deleting model 'RatingCategoryChoice' 48 | db.delete_table(u'review_ratingcategorychoice') 49 | 50 | # Deleting field 'RatingCategory.counts_for_average' 51 | db.delete_column(u'review_ratingcategory', 'counts_for_average') 52 | 53 | 54 | models = { 55 | u'auth.group': { 56 | 'Meta': {'object_name': 'Group'}, 57 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 58 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 59 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 60 | }, 61 | u'auth.permission': { 62 | 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, 63 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 64 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 65 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 66 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 67 | }, 68 | USER_MODEL['model_label']: { 69 | 'Meta': {'object_name': USER_MODEL['object_name']}, 70 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 71 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 72 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 73 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 74 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 75 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 76 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 77 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 78 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 79 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 80 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 81 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 82 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 83 | }, 84 | u'contenttypes.contenttype': { 85 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 86 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 87 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 88 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 89 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 90 | }, 91 | u'generic_positions.objectposition': { 92 | 'Meta': {'object_name': 'ObjectPosition'}, 93 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 94 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 95 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 96 | 'position': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}) 97 | }, 98 | u'review.rating': { 99 | 'Meta': {'ordering': "['category', 'review']", 'object_name': 'Rating'}, 100 | 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.RatingCategory']"}), 101 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 102 | 'review': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ratings'", 'to': u"orm['review.Review']"}), 103 | 'value': ('django.db.models.fields.CharField', [], {'max_length': '20'}) 104 | }, 105 | u'review.ratingcategory': { 106 | 'Meta': {'object_name': 'RatingCategory'}, 107 | 'counts_for_average': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 108 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 109 | 'identifier': ('django.db.models.fields.SlugField', [], {'max_length': '32', 'blank': 'True'}) 110 | }, 111 | u'review.ratingcategorychoice': { 112 | 'Meta': {'object_name': 'RatingCategoryChoice'}, 113 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 114 | 'ratingcategory': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.RatingCategory']"}), 115 | 'value': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}) 116 | }, 117 | u'review.ratingcategorychoicetranslation': { 118 | 'Meta': {'unique_together': "[('language_code', 'master')]", 'object_name': 'RatingCategoryChoiceTranslation', 'db_table': "u'review_ratingcategorychoice_translation'"}, 119 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 120 | 'label': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 121 | 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), 122 | 'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'translations'", 'null': 'True', 'to': u"orm['review.RatingCategoryChoice']"}) 123 | }, 124 | u'review.ratingcategorytranslation': { 125 | 'Meta': {'unique_together': "[('language_code', 'master')]", 'object_name': 'RatingCategoryTranslation', 'db_table': "u'review_ratingcategory_translation'"}, 126 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 127 | 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), 128 | 'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'translations'", 'null': 'True', 'to': u"orm['review.RatingCategory']"}), 129 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}) 130 | }, 131 | u'review.review': { 132 | 'Meta': {'ordering': "['-creation_date']", 'object_name': 'Review'}, 133 | 'average_rating': ('django.db.models.fields.FloatField', [], {'default': '0'}), 134 | 'content': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'blank': 'True'}), 135 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 136 | 'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 137 | 'extra_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'reviews_attached'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}), 138 | 'extra_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 139 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 140 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '5', 'blank': 'True'}), 141 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 142 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['%s']" % USER_MODEL['orm_label'], 'null': 'True', 'blank': 'True'}) 143 | }, 144 | u'review.reviewextrainfo': { 145 | 'Meta': {'ordering': "['type']", 'object_name': 'ReviewExtraInfo'}, 146 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 147 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 148 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 149 | 'review': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.Review']"}), 150 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '256'}) 151 | }, 152 | u'user_media.usermediaimage': { 153 | 'Meta': {'object_name': 'UserMediaImage'}, 154 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), 155 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 156 | 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), 157 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 158 | 'thumb_h': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 159 | 'thumb_w': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 160 | 'thumb_x': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 161 | 'thumb_x2': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 162 | 'thumb_y': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 163 | 'thumb_y2': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 164 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['%s']" % USER_MODEL['orm_label']}) 165 | } 166 | } 167 | 168 | complete_apps = ['review'] 169 | -------------------------------------------------------------------------------- /review/south_migrations/0008_auto__chg_field_ratingcategorychoice_value.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | # -*- coding: utf-8 -*- 3 | import datetime 4 | from south.db import db 5 | from south.v2 import SchemaMigration 6 | from django.db import models 7 | 8 | from ..compat import USER_MODEL 9 | 10 | 11 | class Migration(SchemaMigration): 12 | 13 | def forwards(self, orm): 14 | 15 | # Changing field 'Rating.value' 16 | db.alter_column(u'review_rating', 'value', self.gf('django.db.models.fields.CharField')(max_length=20, null=True)) 17 | 18 | # Changing field 'RatingCategoryChoice.value' 19 | db.alter_column(u'review_ratingcategorychoice', 'value', self.gf('django.db.models.fields.CharField')(max_length=20, null=True)) 20 | 21 | def backwards(self, orm): 22 | 23 | # Changing field 'Rating.value' 24 | db.alter_column(u'review_rating', 'value', self.gf('django.db.models.fields.CharField')(default='', max_length=20)) 25 | 26 | # Changing field 'RatingCategoryChoice.value' 27 | db.alter_column(u'review_ratingcategorychoice', 'value', self.gf('django.db.models.fields.PositiveIntegerField')(null=True)) 28 | 29 | models = { 30 | u'auth.group': { 31 | 'Meta': {'object_name': 'Group'}, 32 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 33 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 34 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 35 | }, 36 | u'auth.permission': { 37 | 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, 38 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 39 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 40 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 41 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 42 | }, 43 | USER_MODEL['model_label']: { 44 | 'Meta': {'object_name': USER_MODEL['object_name']}, 45 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 46 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 47 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 48 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 49 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 50 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 51 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 52 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 53 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 54 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 55 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 56 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 57 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 58 | }, 59 | u'contenttypes.contenttype': { 60 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 61 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 62 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 63 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 64 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 65 | }, 66 | u'generic_positions.objectposition': { 67 | 'Meta': {'object_name': 'ObjectPosition'}, 68 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 69 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 70 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 71 | 'position': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}) 72 | }, 73 | u'review.rating': { 74 | 'Meta': {'ordering': "['category', 'review']", 'object_name': 'Rating'}, 75 | 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.RatingCategory']"}), 76 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 77 | 'review': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ratings'", 'to': u"orm['review.Review']"}), 78 | 'value': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}) 79 | }, 80 | u'review.ratingcategory': { 81 | 'Meta': {'object_name': 'RatingCategory'}, 82 | 'counts_for_average': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 83 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 84 | 'identifier': ('django.db.models.fields.SlugField', [], {'max_length': '32', 'blank': 'True'}) 85 | }, 86 | u'review.ratingcategorychoice': { 87 | 'Meta': {'object_name': 'RatingCategoryChoice'}, 88 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 89 | 'ratingcategory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'choices'", 'to': u"orm['review.RatingCategory']"}), 90 | 'value': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}) 91 | }, 92 | u'review.ratingcategorychoicetranslation': { 93 | 'Meta': {'unique_together': "[('language_code', 'master')]", 'object_name': 'RatingCategoryChoiceTranslation', 'db_table': "u'review_ratingcategorychoice_translation'"}, 94 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 95 | 'label': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 96 | 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), 97 | 'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'translations'", 'null': 'True', 'to': u"orm['review.RatingCategoryChoice']"}) 98 | }, 99 | u'review.ratingcategorytranslation': { 100 | 'Meta': {'unique_together': "[('language_code', 'master')]", 'object_name': 'RatingCategoryTranslation', 'db_table': "u'review_ratingcategory_translation'"}, 101 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 102 | 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), 103 | 'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'translations'", 'null': 'True', 'to': u"orm['review.RatingCategory']"}), 104 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}) 105 | }, 106 | u'review.review': { 107 | 'Meta': {'ordering': "['-creation_date']", 'object_name': 'Review'}, 108 | 'average_rating': ('django.db.models.fields.FloatField', [], {'default': '0'}), 109 | 'content': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'blank': 'True'}), 110 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 111 | 'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 112 | 'extra_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'reviews_attached'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}), 113 | 'extra_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 114 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 115 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '5', 'blank': 'True'}), 116 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 117 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['%s']" % USER_MODEL['orm_label'], 'null': 'True', 'blank': 'True'}) 118 | }, 119 | u'review.reviewextrainfo': { 120 | 'Meta': {'ordering': "['type']", 'object_name': 'ReviewExtraInfo'}, 121 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 122 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 123 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 124 | 'review': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.Review']"}), 125 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '256'}) 126 | }, 127 | u'user_media.usermediaimage': { 128 | 'Meta': {'object_name': 'UserMediaImage'}, 129 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), 130 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 131 | 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), 132 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 133 | 'thumb_h': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 134 | 'thumb_w': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 135 | 'thumb_x': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 136 | 'thumb_x2': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 137 | 'thumb_y': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 138 | 'thumb_y2': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 139 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['%s']" % USER_MODEL['orm_label']}) 140 | } 141 | } 142 | 143 | complete_apps = ['review'] 144 | -------------------------------------------------------------------------------- /review/south_migrations/0009_auto__add_field_ratingcategorytranslation_question.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | # -*- coding: utf-8 -*- 3 | import datetime 4 | from south.db import db 5 | from south.v2 import SchemaMigration 6 | from django.db import models 7 | 8 | from ..compat import USER_MODEL 9 | 10 | 11 | class Migration(SchemaMigration): 12 | 13 | def forwards(self, orm): 14 | # Adding field 'RatingCategoryTranslation.question' 15 | db.add_column(u'review_ratingcategory_translation', 'question', 16 | self.gf('django.db.models.fields.CharField')(max_length=512, null=True, blank=True), 17 | keep_default=False) 18 | 19 | 20 | def backwards(self, orm): 21 | # Deleting field 'RatingCategoryTranslation.question' 22 | db.delete_column(u'review_ratingcategory_translation', 'question') 23 | 24 | 25 | models = { 26 | u'auth.group': { 27 | 'Meta': {'object_name': 'Group'}, 28 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 29 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 30 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 31 | }, 32 | u'auth.permission': { 33 | 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, 34 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 35 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 36 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 37 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 38 | }, 39 | USER_MODEL['model_label']: { 40 | 'Meta': {'object_name': USER_MODEL['object_name']}, 41 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 42 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 43 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 44 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 45 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 46 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 47 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 48 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 49 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 50 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 51 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 52 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 53 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 54 | }, 55 | u'contenttypes.contenttype': { 56 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 57 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 58 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 59 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 60 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 61 | }, 62 | u'generic_positions.objectposition': { 63 | 'Meta': {'object_name': 'ObjectPosition'}, 64 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 65 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 66 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 67 | 'position': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}) 68 | }, 69 | u'review.rating': { 70 | 'Meta': {'ordering': "['category', 'review']", 'object_name': 'Rating'}, 71 | 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.RatingCategory']"}), 72 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 73 | 'review': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ratings'", 'to': u"orm['review.Review']"}), 74 | 'value': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}) 75 | }, 76 | u'review.ratingcategory': { 77 | 'Meta': {'object_name': 'RatingCategory'}, 78 | 'counts_for_average': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 79 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 80 | 'identifier': ('django.db.models.fields.SlugField', [], {'max_length': '32', 'blank': 'True'}) 81 | }, 82 | u'review.ratingcategorychoice': { 83 | 'Meta': {'ordering': "('-value',)", 'object_name': 'RatingCategoryChoice'}, 84 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 85 | 'ratingcategory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'choices'", 'to': u"orm['review.RatingCategory']"}), 86 | 'value': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}) 87 | }, 88 | u'review.ratingcategorychoicetranslation': { 89 | 'Meta': {'unique_together': "[('language_code', 'master')]", 'object_name': 'RatingCategoryChoiceTranslation', 'db_table': "u'review_ratingcategorychoice_translation'"}, 90 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 91 | 'label': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 92 | 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), 93 | 'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'translations'", 'null': 'True', 'to': u"orm['review.RatingCategoryChoice']"}) 94 | }, 95 | u'review.ratingcategorytranslation': { 96 | 'Meta': {'unique_together': "[('language_code', 'master')]", 'object_name': 'RatingCategoryTranslation', 'db_table': "u'review_ratingcategory_translation'"}, 97 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 98 | 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), 99 | 'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'translations'", 'null': 'True', 'to': u"orm['review.RatingCategory']"}), 100 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 101 | 'question': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}) 102 | }, 103 | u'review.review': { 104 | 'Meta': {'ordering': "['-creation_date']", 'object_name': 'Review'}, 105 | 'average_rating': ('django.db.models.fields.FloatField', [], {'default': '0'}), 106 | 'content': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'blank': 'True'}), 107 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 108 | 'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 109 | 'extra_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'reviews_attached'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}), 110 | 'extra_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 111 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 112 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '5', 'blank': 'True'}), 113 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 114 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['%s']" % USER_MODEL['orm_label'], 'null': 'True', 'blank': 'True'}) 115 | }, 116 | u'review.reviewextrainfo': { 117 | 'Meta': {'ordering': "['type']", 'object_name': 'ReviewExtraInfo'}, 118 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 119 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 120 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 121 | 'review': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['review.Review']"}), 122 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '256'}) 123 | }, 124 | u'user_media.usermediaimage': { 125 | 'Meta': {'object_name': 'UserMediaImage'}, 126 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), 127 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 128 | 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), 129 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 130 | 'thumb_h': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 131 | 'thumb_w': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 132 | 'thumb_x': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 133 | 'thumb_x2': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 134 | 'thumb_y': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 135 | 'thumb_y2': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 136 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['%s']" % USER_MODEL['orm_label']}) 137 | } 138 | } 139 | 140 | complete_apps = ['review'] 141 | -------------------------------------------------------------------------------- /review/south_migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitlabstudio/django-review/70d4b5c8d52d9a5615e5d0f5c7f147e15573c566/review/south_migrations/__init__.py -------------------------------------------------------------------------------- /review/templates/review/partials/category_averages.html: -------------------------------------------------------------------------------- 1 | {% load review_tags i18n %} 2 | 3 | {% for category, average_rating in category_averages.iteritems %} 4 | 5 | 6 | 7 | {% endfor %} 8 | 9 | {% get_review_count reviewed_item as review_amount %} 10 | 11 | 12 |
{{ category.name }}:{{ average_rating|floatformat:1 }}
{% trans "Amount of reviews" %}:{{ review_amount }}
13 | -------------------------------------------------------------------------------- /review/templates/review/review_confirm_delete.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Delete review" %}{% endblock %} 5 | 6 | {% block main %} 7 |

{% trans "Delete review" %}

8 |
9 | {% csrf_token %} 10 | 11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /review/templates/review/review_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{{ object }}{% endblock %} 5 | 6 | {% block main %} 7 |

{{ object }}

8 |

{% trans "Review for:" %} {{ object.reviewed_item }} ({{ object.creation_date|date:"SHORT_DATETIME_FORMAT" }})

9 | {% if object.content %} 10 |

{{ object.get_user }} {% trans "wrote" %}:

11 |

{{ object.content }}

12 | {% else %} 13 |

{% blocktrans with user=object.get_user %}{{ user }} reviewed without description.{% endblocktrans %}

14 | {% endif %} 15 | {% if object.ratings.all %} 16 |

{% trans "Ratings" %}{% if object.get_average_rating %} ({{ object.get_average_rating }} {% trans "of" %} 5){% endif %}

17 | 22 | {% endif %} 23 | {% if object.user == request.user and object.is_editable %} 24 |

{% trans "This is your own review." %} {% trans "Change it." %}

25 | {% endif %} 26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /review/templates/review/review_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Create review" %}{% endblock %} 5 | 6 | {% block main %} 7 |

{% trans "Create review" %}

8 |
9 | {% csrf_token %} 10 | {{ form.as_p }} 11 | 12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /review/templates/review/review_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Reviews" %}{% endblock %} 5 | 6 | {% block main %} 7 |

{% trans "Reviews" %}

8 | 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /review/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitlabstudio/django-review/70d4b5c8d52d9a5615e5d0f5c7f147e15573c566/review/templatetags/__init__.py -------------------------------------------------------------------------------- /review/templatetags/review_tags.py: -------------------------------------------------------------------------------- 1 | """Template tags for the ``review`` app.""" 2 | from django.contrib.contenttypes.models import ContentType 3 | from django.template import Library 4 | 5 | from .. import models 6 | 7 | 8 | register = Library() 9 | 10 | 11 | @register.assignment_tag 12 | def get_reviews(obj): 13 | """Simply returns the reviews for an object.""" 14 | ctype = ContentType.objects.get_for_model(obj) 15 | return models.Review.objects.filter(content_type=ctype, object_id=obj.id) 16 | 17 | 18 | @register.assignment_tag 19 | def get_review_average(obj): 20 | """Returns the review average for an object.""" 21 | total = 0 22 | reviews = get_reviews(obj) 23 | if not reviews: 24 | return False 25 | for review in reviews: 26 | average = review.get_average_rating() 27 | if average: 28 | total += review.get_average_rating() 29 | if total > 0: 30 | return total / reviews.count() 31 | return False 32 | 33 | 34 | @register.assignment_tag 35 | def get_review_count(obj): 36 | """Simply returns the review count for an object.""" 37 | return get_reviews(obj).count() 38 | 39 | 40 | @register.inclusion_tag('review/partials/category_averages.html') 41 | def render_category_averages(obj, normalize_to=100): 42 | """Renders all the sub-averages for each category.""" 43 | context = {'reviewed_item': obj} 44 | ctype = ContentType.objects.get_for_model(obj) 45 | reviews = models.Review.objects.filter( 46 | content_type=ctype, object_id=obj.id) 47 | category_averages = {} 48 | for review in reviews: 49 | review_category_averages = review.get_category_averages(normalize_to) 50 | if review_category_averages: 51 | for category, average in review_category_averages.items(): 52 | if category not in category_averages: 53 | category_averages[category] = review_category_averages[ 54 | category] 55 | else: 56 | category_averages[category] += review_category_averages[ 57 | category] 58 | if reviews and category_averages: 59 | for category, average in category_averages.items(): 60 | category_averages[category] = \ 61 | category_averages[category] / models.Rating.objects.filter( 62 | category=category, value__isnull=False, 63 | review__content_type=ctype, 64 | review__object_id=obj.id).exclude(value='').count() 65 | else: 66 | category_averages = {} 67 | for category in models.RatingCategory.objects.filter( 68 | counts_for_average=True): 69 | category_averages[category] = 0.0 70 | context.update({'category_averages': category_averages}) 71 | return context 72 | 73 | 74 | @register.assignment_tag 75 | def total_review_average(obj, normalize_to=100): 76 | """Returns the average for all reviews of the given object.""" 77 | ctype = ContentType.objects.get_for_model(obj) 78 | total_average = 0 79 | reviews = models.Review.objects.filter( 80 | content_type=ctype, object_id=obj.id) 81 | for review in reviews: 82 | total_average += review.get_average_rating(normalize_to) 83 | if reviews: 84 | total_average /= reviews.count() 85 | return total_average 86 | 87 | 88 | @register.assignment_tag 89 | def user_has_reviewed(obj, user): 90 | """Returns True if the user has already reviewed the object.""" 91 | ctype = ContentType.objects.get_for_model(obj) 92 | try: 93 | models.Review.objects.get(user=user, content_type=ctype, 94 | object_id=obj.id) 95 | except models.Review.DoesNotExist: 96 | return False 97 | return True 98 | -------------------------------------------------------------------------------- /review/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitlabstudio/django-review/70d4b5c8d52d9a5615e5d0f5c7f147e15573c566/review/tests/__init__.py -------------------------------------------------------------------------------- /review/tests/forms_tests.py: -------------------------------------------------------------------------------- 1 | """Form tests for the ``review`` app.""" 2 | from django.test import TestCase 3 | 4 | from mixer.backend.django import mixer 5 | 6 | from ..forms import ReviewForm 7 | from ..models import Review, Rating 8 | 9 | 10 | class ReviewFormTestCase(TestCase): 11 | longMessage = True 12 | 13 | def setUp(self): 14 | self.user = mixer.blend('auth.User') 15 | self.content_object = mixer.blend('auth.User') 16 | self.rating_category = mixer.blend('review.RatingCategoryTranslation', 17 | language_code='en-us').master 18 | 19 | def test_form(self): 20 | form = ReviewForm(reviewed_item=self.content_object) 21 | self.assertTrue(form, msg=('Form has been initiated.')) 22 | 23 | with self.settings( 24 | REVIEW_FORM_CHOICE_WIDGET='django.forms.RadioSelect'): 25 | form = ReviewForm(reviewed_item=self.content_object) 26 | self.assertTrue(form, msg=('Form has been initiated.')) 27 | 28 | data = {'category_{0}'.format(self.rating_category.pk): '3'} 29 | form = ReviewForm(reviewed_item=self.content_object, data=data) 30 | self.assertTrue(form.is_valid(), msg=('Form should be valid.')) 31 | 32 | review = form.save() 33 | self.assertEqual(Review.objects.count(), 1, msg=( 34 | 'One review should have been created.')) 35 | self.assertEqual( 36 | Rating.objects.all()[0].review, 37 | Review.objects.all()[0], 38 | msg=('The rating\'s review should be equal the form\'s instance.')) 39 | self.assertEqual( 40 | Rating.objects.all()[0].category.pk, 41 | self.rating_category.pk, 42 | msg=('The rating\'s category should be saved.')) 43 | self.assertEqual(Rating.objects.all()[0].value, '3', msg=( 44 | 'The rating\'s value should be saved.')) 45 | self.assertIsNone(review.user, msg=('User should be None.')) 46 | 47 | form = ReviewForm(user=self.user, reviewed_item=self.content_object, 48 | data=data) 49 | self.assertTrue(form.is_valid(), msg=('Form should be valid.')) 50 | 51 | review = form.save() 52 | self.assertEqual(Review.objects.count(), 2, msg=( 53 | 'Another review should have been created.')) 54 | self.assertIsNotNone(review.user, msg=('User should be existant.')) 55 | 56 | self.new_category = mixer.blend('review.RatingCategoryTranslation', 57 | language_code='en-us') 58 | form = ReviewForm(instance=review, reviewed_item=self.content_object) 59 | self.assertEqual( 60 | form.initial.get('category_{0}'.format(self.rating_category.pk)), 61 | '3', msg=('The form\'s initial should contain the ratings.')) 62 | self.assertFalse( 63 | form.initial.get('category_{0}'.format(self.new_category.pk)), 64 | msg=('The form\'s initial should not contain a new category.')) 65 | 66 | def test_form_with_custom_choices(self): 67 | # Create custom choices 68 | choices = [] 69 | expected_choices = [] 70 | for j in range(1, 4): 71 | i = 5 - j 72 | choices.append(mixer.blend( 73 | 'review.RatingCategoryChoiceTranslation', 74 | language_code='en-us', ratingcategory=self.rating_category, 75 | value=i, label=str(i)).master) 76 | expected_choices.append((u'{}'.format(i), u'{}'.format(i))) 77 | 78 | """ 79 | Disabled. 80 | Find a way to let hvad work with mixer. 81 | 82 | form = ReviewForm(reviewed_item=self.content_object) 83 | self.assertTrue(form, msg=('Form has been initiated.')) 84 | field_name = 'category_{0}'.format(self.rating_category.pk) 85 | self.assertTrue(form[field_name], msg='The field was added') 86 | self.assertEqual(form[field_name].field.choices, expected_choices, 87 | msg=('The field choices were not added correctly from' 88 | ' the RatingCategegoryChoice instances.')) 89 | 90 | data = {'category_{0}'.format(self.rating_category.pk): '5'} 91 | form = ReviewForm(reviewed_item=self.content_object, data=data) 92 | self.assertFalse(form.is_valid(), msg=( 93 | 'When assigning a higher value, than there are' 94 | ' RatingCategoryChoice objects, the form should not be valid.')) 95 | 96 | data = {'category_{0}'.format(self.rating_category.pk): '3'} 97 | form = ReviewForm(reviewed_item=self.content_object, data=data) 98 | self.assertTrue(form.is_valid(), msg=( 99 | 'Even when answering without value and nullifying the category,' 100 | ' the form should be valid. Errors: {0}'.format(form.errors))) 101 | 102 | data = {'category_{0}'.format(self.rating_category.pk): '3'} 103 | form = ReviewForm(reviewed_item=self.content_object, data=data) 104 | self.assertTrue(form.is_valid(), msg=( 105 | 'The form should be valid. Errors: {0}'.format(form.errors))) 106 | 107 | review = form.save() 108 | self.assertEqual(Review.objects.count(), 1, msg=( 109 | 'One review should have been created.')) 110 | self.assertEqual( 111 | Rating.objects.all()[0].review, 112 | Review.objects.all()[0], 113 | msg=('The rating\'s review should be equal the form\'s instance.')) 114 | self.assertEqual( 115 | Rating.objects.all()[0].category.pk, 116 | self.rating_category.pk, 117 | msg=('The rating\'s category should be saved.')) 118 | self.assertEqual(Rating.objects.all()[0].value, '3', msg=( 119 | 'The rating\'s value should be saved.')) 120 | self.assertIsNone(review.user, msg=('User should be None.')) 121 | 122 | mixer.blend('review.RatingCategoryChoiceTranslation', 123 | language_code='en-us', ratingcategory=self.rating_category, 124 | value=0, label='0') 125 | data = {'content': 'foo', 126 | 'category_{0}'.format(self.rating_category.pk): None} 127 | form = ReviewForm(reviewed_item=self.content_object, data=data) 128 | self.assertFalse(form.is_valid(), msg=( 129 | 'You should not be able to rate a category with None if there' 130 | ' is no None choice.')) 131 | 132 | data = {'content': 'foobar'} 133 | form = ReviewForm(reviewed_item=self.content_object, data=data) 134 | self.assertFalse(form.is_valid(), msg=( 135 | 'Without any choice selected, the form should be invalid.')) 136 | 137 | """ 138 | -------------------------------------------------------------------------------- /review/tests/models_tests.py: -------------------------------------------------------------------------------- 1 | """Tests for the models of the review app.""" 2 | from django.contrib.contenttypes.models import ContentType 3 | from django.test import TestCase 4 | from django.utils.timezone import now, timedelta 5 | 6 | from mixer.backend.django import mixer 7 | 8 | from .test_app.models import WeatherCondition 9 | 10 | 11 | class ReviewTestCase(TestCase): 12 | longMessage = True 13 | 14 | def setUp(self): 15 | self.review = mixer.blend( 16 | 'review.Review', content_type=ContentType.objects.get_for_model( 17 | WeatherCondition)) 18 | 19 | def test_instance(self): 20 | self.assertTrue(self.review.pk, msg=( 21 | 'Review model should have been created.')) 22 | 23 | def test_get_user(self): 24 | self.assertEqual(self.review.get_user(), 'Anonymous', msg=( 25 | 'Should return anonymous.')) 26 | self.user = mixer.blend('auth.User') 27 | self.review.user = self.user 28 | self.assertEqual(self.review.get_user(), self.user.email, msg=( 29 | 'Should return a user\'s email.')) 30 | 31 | def test_get_average_rating(self): 32 | self.assertFalse(self.review.get_average_rating(), msg=( 33 | 'If there are no ratings, it should return False.')) 34 | mixer.blend('review.Rating', review=self.review, value='2') 35 | mixer.blend('review.Rating', review=self.review, value='4') 36 | self.assertEqual(self.review.get_average_rating(), 3, msg=( 37 | 'Should return the average rating value.')) 38 | 39 | mixer.blend('review.Rating', review=self.review, value='') 40 | mixer.blend('review.Rating', category__counts_for_average=False, 41 | review=self.review, value=0.0) 42 | self.assertEqual(self.review.get_average_rating(), 3, msg=( 43 | 'Should return the average rating value and exclude the nullified' 44 | ' ones.')) 45 | 46 | def test_get_average_rating_with_custom_choices(self): 47 | self.assertFalse(self.review.get_average_rating(), msg=( 48 | 'If there are no ratings, it should return False.')) 49 | rating1 = mixer.blend('review.Rating', review=self.review, value='4') 50 | # we create choices to simulate, that the previous value was the max 51 | for i in range(0, 5): 52 | mixer.blend('review.RatingCategoryChoiceTranslation', 53 | language_code='en-us', 54 | ratingcategory=rating1.category, value=i) 55 | rating2 = mixer.blend('review.Rating', review=self.review, value='6') 56 | # we create choices to simulate, that the previous value was the max 57 | for i in range(0, 7): 58 | mixer.blend('review.RatingCategoryChoiceTranslation', 59 | language_code='en-us', 60 | ratingcategory=rating2.category, value=i) 61 | mixer.blend('review.Rating', category=rating2.category, 62 | review=self.review, value='6') 63 | mixer.blend('review.Rating', category=rating2.category, 64 | review=self.review, value=None) 65 | # testing the absolute max voting 66 | self.assertEqual(self.review.get_average_rating(6), 6, msg=( 67 | 'Should return the average rating value.')) 68 | self.assertEqual(self.review.get_average_rating(4), 4, msg=( 69 | 'Should return the average rating value.')) 70 | self.assertEqual(self.review.get_average_rating(100), 100, msg=( 71 | 'Should return the average rating value.')) 72 | 73 | # testing the category averages 74 | """ 75 | Fix those tests! 76 | 77 | self.assertEqual( 78 | self.review.get_category_averages(6), 79 | {rating1.category: 6.0, rating2.category: 6.0}, 80 | msg=('Should return the average ratings for the category.')) 81 | 82 | self.assertEqual( 83 | self.review.get_category_averages(), 84 | {rating1.category: 6.0, rating2.category: 6.0}, 85 | msg=('Should return the average ratings for the category.')) 86 | 87 | self.assertEqual( 88 | self.review.get_category_averages(100), 89 | {rating1.category: 100.0, rating2.category: 100.0}, 90 | msg=('Should return the average ratings for the category.')) 91 | 92 | # these ratings should not change results and should just be ignored 93 | mixer.blend('review.Rating', category=rating2.category, 94 | review=self.review, value='') 95 | mixer.blend('review.Rating', review=self.review, value='') 96 | self.assertEqual(self.review.get_average_rating(6), 6, msg=( 97 | 'Should return the average rating value.')) 98 | self.assertEqual(self.review.get_average_rating(4), 4, msg=( 99 | 'Should return the average rating value.')) 100 | self.assertEqual(self.review.get_average_rating(100), 100, msg=( 101 | 'Should return the average rating value.')) 102 | 103 | # altering the ratings to get a very low voting 104 | rating1.value = '1' 105 | rating1.save() 106 | rating2.value = '1' 107 | rating2.save() 108 | rating3.value = '1' 109 | rating3.save() 110 | self.assertEqual(self.review.get_average_rating(6), 1.25, 111 | msg=('Should return the average rating value.')) 112 | self.assertEqual(self.review.get_average_rating(4), 0.8333333333333333, 113 | msg=('Should return the average rating value.')) 114 | self.assertEqual( 115 | self.review.get_average_rating(100), 116 | 20.833333333333336, msg=( 117 | 'Should return the average rating value.')) 118 | 119 | # and finally the lowest possible voting 120 | rating1.value = '0' 121 | rating1.save() 122 | rating2.value = '0' 123 | rating2.save() 124 | rating3.value = '0' 125 | rating3.save() 126 | self.assertEqual(self.review.get_average_rating(6), 0, 127 | msg=('Should return the average rating value.')) 128 | self.assertEqual(self.review.get_average_rating(4), 0, 129 | msg=('Should return the average rating value.')) 130 | self.assertEqual(self.review.get_average_rating(100), 0, 131 | msg=('Should return the average rating value.')) 132 | 133 | """ 134 | 135 | def test_is_editable(self): 136 | self.assertTrue(self.review.is_editable(), msg=( 137 | 'Should be editable, if period setting is not set.')) 138 | with self.settings(REVIEW_UPDATE_PERIOD=1): 139 | self.assertTrue(self.review.is_editable(), msg=( 140 | 'Should be editable, if period has not ended yet.')) 141 | self.review.creation_date = now() - timedelta(days=1) 142 | self.review.save() 143 | self.assertFalse(self.review.is_editable(), msg=( 144 | 'Should return False, if period has ended.')) 145 | 146 | 147 | class ReviewExtraInfoTestCase(TestCase): 148 | longMessage = True 149 | 150 | def setUp(self): 151 | self.extra_info = mixer.blend( 152 | 'review.ReviewExtraInfo', 153 | review=mixer.blend('review.Review', 154 | content_type=ContentType.objects.get_for_model( 155 | WeatherCondition)), 156 | content_type=ContentType.objects.get_for_model(WeatherCondition)) 157 | 158 | def test_instance(self): 159 | self.assertTrue(self.extra_info.pk, msg=( 160 | 'Review extra info model should have been created.')) 161 | 162 | 163 | class RatingCategoryTestCase(TestCase): 164 | longMessage = True 165 | 166 | def setUp(self): 167 | self.category = mixer.blend('review.RatingCategoryTranslation', 168 | language_code='en-us') 169 | 170 | def test_instance(self): 171 | self.assertTrue(self.category.pk, msg=( 172 | 'Rating category model should have been created.')) 173 | 174 | 175 | class RatingCategoryChoiceTestCase(TestCase): 176 | """Tests for the ``RatingCategoryChoice`` model class.""" 177 | longMessage = True 178 | 179 | def test_instantiation(self): 180 | """Test instantiation of the ``RatingCategoryChoice`` model.""" 181 | ratingcategorychoice = mixer.blend( 182 | 'review.RatingCategoryChoiceTranslation', language_code='en-us') 183 | self.assertTrue(ratingcategorychoice.pk) 184 | 185 | 186 | class RatingTestCase(TestCase): 187 | longMessage = True 188 | 189 | def setUp(self): 190 | review = mixer.blend( 191 | 'review.Review', content_type=ContentType.objects.get_for_model( 192 | WeatherCondition)) 193 | self.rating = mixer.blend('review.Rating', review=review) 194 | 195 | def test_instance(self): 196 | self.assertTrue(self.rating.pk, msg=( 197 | 'Rating model should have been created.')) 198 | -------------------------------------------------------------------------------- /review/tests/review_tags_tests.py: -------------------------------------------------------------------------------- 1 | """Test for the template tags of the ``review`` app.""" 2 | from django.contrib.contenttypes.models import ContentType 3 | from django.test import TestCase 4 | 5 | from mixer.backend.django import mixer 6 | 7 | from ..templatetags import review_tags 8 | from .test_app.models import WeatherCondition 9 | 10 | 11 | class GetReviewsTestCase(TestCase): 12 | """Tests for the ``get_reviews`` template tag.""" 13 | longMessage = True 14 | 15 | def setUp(self): 16 | self.reviewed_item = mixer.blend('test_app.WeatherCondition') 17 | self.review = mixer.blend( 18 | 'review.Review', 19 | content_type=ContentType.objects.get_for_model(WeatherCondition), 20 | reviewed_item=self.reviewed_item) 21 | 22 | def test_tag(self): 23 | self.assertEqual(len(review_tags.get_reviews(self.reviewed_item)), 1) 24 | self.review.delete() 25 | self.assertEqual(len(review_tags.get_reviews(self.reviewed_item)), 0) 26 | 27 | 28 | class GetReviewAverageTestCase(TestCase): 29 | """Tests for the ``get_review_average`` template tag.""" 30 | longMessage = True 31 | 32 | def setUp(self): 33 | self.reviewed_item = mixer.blend('test_app.WeatherCondition') 34 | 35 | def test_tag(self): 36 | self.assertFalse(review_tags.get_review_average(self.reviewed_item)) 37 | review = mixer.blend( 38 | 'review.Review', reviewed_item=self.reviewed_item, 39 | content_type=ContentType.objects.get_for_model(WeatherCondition), 40 | object_id=self.reviewed_item.pk, 41 | ) 42 | self.assertEqual(review_tags.get_review_average(self.reviewed_item), 0) 43 | rating = mixer.blend('review.Rating', review=review) 44 | mixer.blend('review.Rating', review=review, category=rating.category) 45 | """ 46 | Fix those tests! 47 | self.assertEqual(review_tags.get_review_average(self.reviewed_item), 3) 48 | 49 | """ 50 | 51 | 52 | class GetReviewCountTestCase(TestCase): 53 | """Tests for the ``get_review_count`` template tag.""" 54 | longMessage = True 55 | 56 | def setUp(self): 57 | self.reviewed_item = mixer.blend('test_app.WeatherCondition') 58 | self.review = mixer.blend( 59 | 'review.Review', 60 | content_type=ContentType.objects.get_for_model(WeatherCondition), 61 | reviewed_item=self.reviewed_item) 62 | 63 | def test_tag(self): 64 | self.assertEqual( 65 | review_tags.get_review_count(self.reviewed_item), 1) 66 | self.review.delete() 67 | self.assertEqual( 68 | review_tags.get_review_count(self.reviewed_item), 0) 69 | 70 | 71 | class RenderCategoryAveragesTestCase(TestCase): 72 | """Tests for the ``render_category_averages`` template tag.""" 73 | longMessage = True 74 | 75 | def setUp(self): 76 | self.reviewed_item = mixer.blend('test_app.WeatherCondition') 77 | self.review = mixer.blend( 78 | 'review.Review', 79 | content_type=ContentType.objects.get_for_model(WeatherCondition), 80 | reviewed_item=self.reviewed_item) 81 | 82 | def test_tag(self): 83 | expected_value = { 84 | 'reviewed_item': self.reviewed_item, 85 | 'category_averages': {}, 86 | } 87 | self.assertEqual( 88 | review_tags.render_category_averages(self.reviewed_item, 5), 89 | expected_value) 90 | 91 | self.rating = mixer.blend( 92 | 'review.Rating', review=self.review, value='2') 93 | expected_value = { 94 | 'reviewed_item': self.reviewed_item, 95 | 'category_averages': {self.rating.category: 2.0}, 96 | } 97 | self.assertEqual( 98 | review_tags.render_category_averages(self.reviewed_item, 5), 99 | expected_value) 100 | 101 | expected_value = { 102 | 'reviewed_item': self.reviewed_item, 103 | 'category_averages': {self.rating.category: 40.0}, 104 | } 105 | self.assertEqual( 106 | review_tags.render_category_averages(self.reviewed_item, 100), 107 | expected_value) 108 | 109 | def test_tag_more_extensively(self): 110 | self.review = mixer.blend( 111 | 'review.Review', reviewed_item=self.reviewed_item, 112 | object_id=self.reviewed_item.pk, 113 | content_type=ContentType.objects.get_for_model(WeatherCondition)) 114 | # the test_tag case was merely to cover some basic functionality. This 115 | # goes more in depth. 116 | rating1 = mixer.blend('review.Rating', review=self.review, value='4') 117 | # we create choices to simulate, that the previous value was the max 118 | for i in range(0, 5): 119 | mixer.blend('review.RatingCategoryChoiceTranslation', 120 | language_code='en-us', 121 | ratingcategory=rating1.category, value=i) 122 | rating2 = mixer.blend('review.Rating', review=self.review, value='4') 123 | # we create choices to simulate, that the previous value was the max 124 | for i in range(0, 7): 125 | mixer.blend('review.RatingCategoryChoiceTranslation', 126 | language_code='en-us', 127 | ratingcategory=rating2.category, value=i) 128 | mixer.blend('review.Rating', category=rating2.category, 129 | review=self.review, value=None) 130 | mixer.blend('review.Rating', category=rating2.category, 131 | review=self.review, value=None) 132 | 133 | """ 134 | Fix those tests! 135 | expected_value = { 136 | 'reviewed_item': self.reviewed_item, 137 | 'category_averages': { 138 | rating1.category: 6.0, 139 | rating2.category: 4.0, 140 | }, 141 | } 142 | 143 | self.assertEqual( 144 | review_tags.render_category_averages(self.reviewed_item, 6), 145 | expected_value, 146 | ) 147 | 148 | """ 149 | 150 | 151 | class TotalReviewAverageTestCase(TestCase): 152 | """Tests for the ``total_review_average`` template tag.""" 153 | longMessage = True 154 | 155 | def setUp(self): 156 | self.content_object = mixer.blend('test_app.WeatherCondition') 157 | self.review = mixer.blend( 158 | 'review.Review', reviewed_item=self.content_object, 159 | content_type=ContentType.objects.get_for_model(WeatherCondition), 160 | ) 161 | self.rating1 = mixer.blend('review.Rating', review=self.review, 162 | value='4') 163 | # we create choices to simulate, that the previous value was the max 164 | for i in range(0, 5): 165 | mixer.blend('review.RatingCategoryChoiceTranslation', 166 | language_code='en-us', 167 | ratingcategory=self.rating1.category, value=i) 168 | self.rating2 = mixer.blend('review.Rating', review=self.review, 169 | value='6') 170 | # we create choices to simulate, that the previous value was the max 171 | for i in range(0, 7): 172 | mixer.blend('review.RatingCategoryChoiceTranslation', 173 | language_code='en-us', 174 | ratingcategory=self.rating2.category, value=i) 175 | 176 | def test_tag(self): 177 | self.assertEqual( 178 | review_tags.total_review_average(self.content_object), 100) 179 | mixer.blend('review.Rating', category=self.rating1.category, 180 | review=self.review, value='0') 181 | mixer.blend('review.Rating', category=self.rating2.category, 182 | review=self.review, value='0') 183 | self.assertEqual( 184 | review_tags.total_review_average(self.content_object), 50) 185 | mixer.blend('review.Rating', category=self.rating1.category, 186 | review=self.review, value='') 187 | mixer.blend('review.Rating', category=self.rating2.category, 188 | review=self.review, value='') 189 | self.assertEqual( 190 | review_tags.total_review_average(self.content_object), 50) 191 | self.assertEqual( 192 | review_tags.total_review_average(self.content_object, 10), 5) 193 | self.assertEqual( 194 | review_tags.total_review_average(self.content_object, 5), 2.5) 195 | 196 | 197 | class UserHasReviewedTestCase(TestCase): 198 | """Tests for the ``user_has_reviewed`` template tag.""" 199 | longMessage = True 200 | 201 | def setUp(self): 202 | self.user = mixer.blend('auth.User') 203 | self.content_object = mixer.blend('test_app.WeatherCondition') 204 | self.review = mixer.blend( 205 | 'review.Review', user=self.user, reviewed_item=self.content_object, 206 | content_type=ContentType.objects.get_for_model(WeatherCondition), 207 | ) 208 | self.other_user = mixer.blend('auth.User') 209 | 210 | def test_tag(self): 211 | self.assertTrue( 212 | review_tags.user_has_reviewed(self.content_object, self.user)) 213 | self.assertFalse( 214 | review_tags.user_has_reviewed(self.content_object, 215 | self.other_user)) 216 | -------------------------------------------------------------------------------- /review/tests/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | These settings are used by the ``manage.py`` command. 3 | With normal tests we want to use the fastest possible way which is an 4 | in-memory sqlite database but if you want to create South migrations you 5 | need a persistant database. 6 | Unfortunately there seems to be an issue with either South or syncdb so that 7 | defining two routers ("default" and "south") does not work. 8 | """ 9 | from distutils.version import StrictVersion 10 | 11 | import django 12 | 13 | from .test_settings import * # NOQA 14 | 15 | 16 | DATABASES = { 17 | 'default': { 18 | 'ENGINE': 'django.db.backends.sqlite3', 19 | 'NAME': 'db.sqlite', 20 | } 21 | } 22 | 23 | django_version = django.get_version() 24 | if StrictVersion(django_version) < StrictVersion('1.7'): 25 | INSTALLED_APPS.append('south', ) # NOQA 26 | -------------------------------------------------------------------------------- /review/tests/test_app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitlabstudio/django-review/70d4b5c8d52d9a5615e5d0f5c7f147e15573c566/review/tests/test_app/__init__.py -------------------------------------------------------------------------------- /review/tests/test_app/forms.py: -------------------------------------------------------------------------------- 1 | """Forms for the ``test_app`` app.""" 2 | from django import forms 3 | 4 | from review.forms import ReviewForm 5 | from review.models import ReviewExtraInfo 6 | from .models import WeatherCondition 7 | 8 | 9 | class CustomReviewForm(ReviewForm): 10 | """Form to make use of the review extra info.""" 11 | def __init__(self, reviewed_item, user=None, *args, **kwargs): 12 | super(CustomReviewForm, self).__init__( 13 | reviewed_item, user, *args, **kwargs) 14 | choices = [(x.pk, x.name) for x in WeatherCondition.objects.all()] 15 | self.fields['weather_conditions'] = forms.fields.ChoiceField( 16 | choices=choices) 17 | 18 | def save(self, *args, **kwargs): 19 | self.instance = super(CustomReviewForm, self).save(*args, **kwargs) 20 | ReviewExtraInfo.objects.create( 21 | type='weather_conditions', 22 | review=self.instance, 23 | content_object=WeatherCondition.objects.get( 24 | pk=self.cleaned_data['weather_conditions']), 25 | ) 26 | return self.instance 27 | -------------------------------------------------------------------------------- /review/tests/test_app/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class WeatherCondition(models.Model): 5 | name = models.CharField(max_length=20) 6 | -------------------------------------------------------------------------------- /review/tests/test_app/templates/400.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitlabstudio/django-review/70d4b5c8d52d9a5615e5d0f5c7f147e15573c566/review/tests/test_app/templates/400.html -------------------------------------------------------------------------------- /review/tests/test_app/templates/500.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitlabstudio/django-review/70d4b5c8d52d9a5615e5d0f5c7f147e15573c566/review/tests/test_app/templates/500.html -------------------------------------------------------------------------------- /review/tests/test_app/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% block title %}{% endblock %} 5 | 6 | 7 | 8 | {% block main %}{% endblock %} 9 | 10 | 11 | -------------------------------------------------------------------------------- /review/tests/test_settings.py: -------------------------------------------------------------------------------- 1 | """Settings that need to be set in order to run the tests.""" 2 | import os 3 | 4 | 5 | DEBUG = True 6 | 7 | SITE_ID = 1 8 | 9 | APP_ROOT = os.path.abspath( 10 | os.path.join(os.path.dirname(__file__), '..')) 11 | 12 | 13 | DATABASES = { 14 | 'default': { 15 | 'ENGINE': 'django.db.backends.sqlite3', 16 | 'NAME': ':memory:', 17 | } 18 | } 19 | 20 | ROOT_URLCONF = 'review.tests.urls' 21 | 22 | STATIC_URL = '/static/' 23 | STATIC_ROOT = os.path.join(APP_ROOT, '../app_static') 24 | MEDIA_ROOT = os.path.join(APP_ROOT, '../app_media') 25 | STATICFILES_DIRS = ( 26 | os.path.join(APP_ROOT, 'static'), 27 | ) 28 | 29 | TEMPLATES = [{ 30 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 31 | 'APP_DIRS': True, 32 | 'DIRS': [os.path.join(APP_ROOT, 'tests/test_app/templates')], 33 | 'OPTIONS': { 34 | 'context_processors': ( 35 | 'django.contrib.auth.context_processors.auth', 36 | 'django.template.context_processors.i18n', 37 | 'django.template.context_processors.request', 38 | 'django.template.context_processors.media', 39 | 'django.template.context_processors.static', 40 | ) 41 | } 42 | }] 43 | 44 | EXTERNAL_APPS = [ 45 | 'django.contrib.admin', 46 | 'django.contrib.admindocs', 47 | 'django.contrib.auth', 48 | 'django.contrib.contenttypes', 49 | 'django.contrib.messages', 50 | 'django.contrib.sessions', 51 | 'django.contrib.staticfiles', 52 | 'django.contrib.sitemaps', 53 | 'django.contrib.sites', 54 | 'generic_positions', 55 | 'hvad', 56 | 'user_media', 57 | 'easy_thumbnails', 58 | ] 59 | 60 | INTERNAL_APPS = [ 61 | 'review', 62 | 'review.tests.test_app', 63 | ] 64 | 65 | INSTALLED_APPS = EXTERNAL_APPS + INTERNAL_APPS 66 | 67 | SECRET_KEY = 'foobar' 68 | 69 | MIDDLEWARE_CLASSES = ( 70 | 'django.contrib.sessions.middleware.SessionMiddleware', 71 | 'django.middleware.common.CommonMiddleware', 72 | 'django.middleware.csrf.CsrfViewMiddleware', 73 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 74 | 'django.contrib.messages.middleware.MessageMiddleware', 75 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 76 | ) 77 | 78 | REVIEW_FORM_CHOICE_WIDGET = 'django.forms.widgets.RadioSelect' 79 | -------------------------------------------------------------------------------- /review/tests/urls.py: -------------------------------------------------------------------------------- 1 | """URLs to run the tests.""" 2 | from django.conf.urls import include, url 3 | from django.contrib import admin 4 | from django.views.generic import ListView 5 | 6 | from ..models import Review 7 | 8 | 9 | admin.autodiscover() 10 | 11 | urlpatterns = [ 12 | url(r'^review-listing/', ListView.as_view(model=Review), 13 | name='review_list'), 14 | url(r'^admin/', include(admin.site.urls)), 15 | url(r'^umedia/', include('user_media.urls')), 16 | url(r'^review/', include('review.urls')), 17 | ] 18 | -------------------------------------------------------------------------------- /review/tests/views_tests.py: -------------------------------------------------------------------------------- 1 | """View tests for the ``review`` app.""" 2 | from django.contrib.contenttypes.models import ContentType 3 | from django.core.urlresolvers import reverse 4 | from django.test import TestCase 5 | from django.utils import timezone 6 | 7 | from django_libs.tests.mixins import ViewRequestFactoryTestMixin 8 | from mixer.backend.django import mixer 9 | 10 | from .. import views 11 | from ..models import Review 12 | from .test_app.models import WeatherCondition 13 | 14 | 15 | class ReviewCreateViewTestCase(ViewRequestFactoryTestMixin, TestCase): 16 | view_class = views.ReviewCreateView 17 | 18 | def setUp(self): 19 | self.content_object = mixer.blend('auth.User') 20 | self.user = mixer.blend('auth.User', first_name='') 21 | 22 | def get_view_name(self): 23 | return 'review_create' 24 | 25 | def get_view_kwargs(self): 26 | return { 27 | 'content_type': ContentType.objects.get_for_model( 28 | self.content_object), 29 | 'object_id': self.content_object.pk, 30 | } 31 | 32 | def user_has_perm(self, user): 33 | # Simulates permission for the current user 34 | if user.first_name: 35 | return True 36 | return False 37 | 38 | def test_view(self): 39 | wrong_kwargs = { 40 | 'content_type': 'Foo', 41 | 'object_id': self.content_object.pk, 42 | } 43 | self.is_not_callable(kwargs=wrong_kwargs) 44 | 45 | wrong_kwargs = { 46 | 'content_type': ContentType.objects.get_for_model( 47 | self.content_object), 48 | 'object_id': '999', 49 | } 50 | self.is_not_callable(kwargs=wrong_kwargs) 51 | 52 | self.is_callable(user=self.user, msg=( 53 | 'View should be callable by an authenticated user.')) 54 | 55 | with self.settings(REVIEW_AVOID_MULTIPLE_REVIEWS=True): 56 | def has_perm(u, item): 57 | return self.user_has_perm(u) 58 | with self.settings(REVIEW_PERMISSION_FUNCTION=has_perm): 59 | self.is_not_callable(post=True, user=self.user, msg=( 60 | 'View should not be callable due to missing permissions.')) 61 | self.assertEqual(Review.objects.count(), 0, msg=( 62 | 'No reviews should\'ve been created.')) 63 | self.user.first_name = 'Foo' 64 | self.user.save() 65 | self.is_postable( 66 | user=self.user, to_url_name='review_detail', msg=( 67 | 'View should be callable by an authenticated user.')) 68 | self.assertEqual(Review.objects.count(), 1, msg=( 69 | 'One review should\'ve been created.')) 70 | self.is_postable( 71 | to=reverse('review_update', kwargs={ 72 | 'pk': Review.objects.all()[0].pk}), 73 | user=self.user, msg=( 74 | 'View should redirect, if review alreasy exists.')) 75 | self.assertEqual(Review.objects.count(), 1, msg=( 76 | 'No new review should\'ve been created.')) 77 | 78 | with self.settings(REVIEW_CUSTOM_FORM='test_app.FooReviewForm'): 79 | self.is_callable(user=self.user) 80 | 81 | with self.settings(REVIEW_CUSTOM_FORM='foo.BarForm'): 82 | self.is_callable(user=self.user) 83 | 84 | with self.settings( 85 | REVIEW_CUSTOM_FORM='test_app.forms.CustomReviewForm'): 86 | data = { 87 | 'weather_conditions': mixer.blend( 88 | 'test_app.WeatherCondition').pk, 89 | } 90 | self.is_postable(data=data, user=self.user, 91 | to_url_name='review_detail') 92 | 93 | with self.settings(REVIEW_UPDATE_SUCCESS_URL='review_list'): 94 | self.is_postable(data=data, user=self.user, 95 | to=reverse('review_list')) 96 | 97 | with self.settings(REVIEW_ALLOW_ANONYMOUS=True): 98 | self.is_callable(msg=( 99 | 'View should be callable as an anonymous user.')) 100 | 101 | 102 | class ReviewDetailViewTestCase(ViewRequestFactoryTestMixin, TestCase): 103 | view_class = views.ReviewDetailView 104 | 105 | def setUp(self): 106 | self.review = mixer.blend( 107 | 'review.Review', 108 | object_id=mixer.blend('test_app.WeatherCondition').pk, 109 | content_type=ContentType.objects.get_for_model(WeatherCondition)) 110 | 111 | def get_view_name(self): 112 | return 'review_detail' 113 | 114 | def get_view_kwargs(self): 115 | return {'pk': self.review.pk} 116 | 117 | def test_view(self): 118 | self.is_callable() 119 | 120 | 121 | class ReviewUpdateViewTestCase(ViewRequestFactoryTestMixin, TestCase): 122 | view_class = views.ReviewUpdateView 123 | 124 | def setUp(self): 125 | self.user = mixer.blend('auth.User') 126 | self.other_user = mixer.blend('auth.User') 127 | self.review = mixer.blend( 128 | 'review.Review', user=self.user, 129 | object_id=mixer.blend('test_app.WeatherCondition').pk, 130 | content_type=ContentType.objects.get_for_model(WeatherCondition)) 131 | 132 | def get_view_name(self): 133 | return 'review_update' 134 | 135 | def get_view_kwargs(self): 136 | return {'pk': self.review.pk} 137 | 138 | def test_view(self): 139 | self.is_not_callable(user=self.other_user) 140 | self.is_callable(user=self.user) 141 | 142 | with self.settings(REVIEW_UPDATE_PERIOD=1): 143 | self.is_callable(user=self.user, msg=( 144 | 'Should be callable, if period hasn\'t ended yet.')) 145 | self.review.creation_date = timezone.now() - timezone.timedelta( 146 | seconds=120) 147 | self.review.save() 148 | self.redirects( 149 | to=reverse('review_detail', kwargs={'pk': self.review.pk}), 150 | user=self.user, msg=('Should redirect, if period has ended.')) 151 | 152 | 153 | class ReviewDeleteViewTestCase(ViewRequestFactoryTestMixin, TestCase): 154 | view_class = views.ReviewDeleteView 155 | 156 | def setUp(self): 157 | self.user = mixer.blend('auth.User') 158 | self.review = mixer.blend( 159 | 'review.Review', user=self.user, 160 | object_id=mixer.blend('test_app.WeatherCondition').pk, 161 | content_type=ContentType.objects.get_for_model(WeatherCondition)) 162 | 163 | def get_view_name(self): 164 | return 'review_delete' 165 | 166 | def get_view_kwargs(self): 167 | return {'pk': self.review.pk} 168 | 169 | def test_view(self): 170 | self.is_postable(user=self.user, to='/') 171 | self.review = mixer.blend( 172 | 'review.Review', user=self.user, 173 | object_id=mixer.blend('test_app.WeatherCondition').pk, 174 | content_type=ContentType.objects.get_for_model(WeatherCondition)) 175 | with self.settings(REVIEW_DELETION_SUCCESS_URL='review_list'): 176 | self.is_postable(user=self.user, to_url_name='review_list') 177 | -------------------------------------------------------------------------------- /review/urls.py: -------------------------------------------------------------------------------- 1 | """URLs for the review app.""" 2 | from django.conf.urls import url 3 | 4 | from . import views 5 | 6 | 7 | urlpatterns = [ 8 | url(r'^(?P\d+)/delete/$', 9 | views.ReviewDeleteView.as_view(), 10 | name='review_delete'), 11 | url(r'^(?P\d+)/update/$', 12 | views.ReviewUpdateView.as_view(), 13 | name='review_update'), 14 | url(r'^(?P\d+)/$', 15 | views.ReviewDetailView.as_view(), 16 | name='review_detail'), 17 | url(r'^(?P[-\w]+)/(?P\d+)/create/$', 18 | views.ReviewCreateView.as_view(), 19 | name='review_create'), 20 | ] 21 | -------------------------------------------------------------------------------- /review/views.py: -------------------------------------------------------------------------------- 1 | """Views for the review app.""" 2 | import importlib 3 | 4 | from django.conf import settings 5 | from django.contrib.auth.decorators import login_required 6 | from django.contrib.contenttypes.models import ContentType 7 | from django.core.urlresolvers import reverse 8 | from django.db.models import ObjectDoesNotExist 9 | from django.http import Http404, HttpResponseRedirect 10 | from django.utils import timezone 11 | from django.views.generic import CreateView, DeleteView, DetailView, UpdateView 12 | 13 | from .forms import ReviewForm 14 | from .models import Review 15 | 16 | 17 | # ------ MIXINS ------ # 18 | 19 | class ReviewViewMixin(object): 20 | model = Review 21 | 22 | def dispatch(self, request, *args, **kwargs): 23 | # Check, if user needs to be logged in 24 | if getattr(settings, 'REVIEW_ALLOW_ANONYMOUS', False): 25 | return super(ReviewViewMixin, self).dispatch( 26 | request, *args, **kwargs) 27 | return login_required(super(ReviewViewMixin, self).dispatch)( 28 | request, *args, **kwargs) 29 | 30 | def get_form_class(self): 31 | if getattr(settings, 'REVIEW_CUSTOM_FORM', False): 32 | app_label, class_name = settings.REVIEW_CUSTOM_FORM.rsplit('.', 1) 33 | try: 34 | return getattr(importlib.import_module(app_label), class_name, 35 | ReviewForm) 36 | except ImportError: 37 | pass 38 | return ReviewForm 39 | 40 | def get_form_kwargs(self, *args, **kwargs): 41 | kwargs = super(ReviewViewMixin, self).get_form_kwargs(*args, **kwargs) 42 | kwargs.update({'reviewed_item': self.reviewed_item}) 43 | if self.request.user.is_authenticated(): 44 | kwargs.update({'user': self.request.user}) 45 | return kwargs 46 | 47 | def get_success_url(self): 48 | success_url = getattr(settings, 'REVIEW_UPDATE_SUCCESS_URL', None) 49 | if success_url is not None: 50 | if callable(success_url): 51 | return success_url(self.object) 52 | else: 53 | return reverse(success_url) 54 | return reverse('review_detail', kwargs={'pk': self.object.pk}) 55 | 56 | 57 | class ReviewUpdateMixin(object): 58 | """Mixin to provide update functions for a ``Review`` instance.""" 59 | def dispatch(self, request, *args, **kwargs): 60 | self.kwargs = kwargs 61 | self.object = self.get_object() 62 | if not self.object.user or self.object.user != request.user: 63 | raise Http404 64 | # Check, if update period is set and has ended 65 | if getattr(settings, 'REVIEW_UPDATE_PERIOD', False): 66 | period_end = self.object.creation_date + timezone.timedelta( 67 | seconds=getattr(settings, 'REVIEW_UPDATE_PERIOD') * 60) 68 | if timezone.now() > period_end: 69 | return HttpResponseRedirect( 70 | reverse('review_detail', kwargs={'pk': self.object.pk})) 71 | self.reviewed_item = self.object.reviewed_item 72 | return super(ReviewUpdateMixin, self).dispatch( 73 | request, *args, **kwargs) 74 | 75 | 76 | # ------ MODEL VIEWS ------ # 77 | 78 | class ReviewCreateView(ReviewViewMixin, CreateView): 79 | """View to create a new ``Review`` instance.""" 80 | def dispatch(self, request, *args, **kwargs): 81 | # Check, if content type exists 82 | try: 83 | self.ctype = ContentType.objects.get( 84 | model=kwargs.get('content_type')) 85 | except ContentType.DoesNotExist: 86 | raise Http404 87 | 88 | # Check, if reviewed item exists 89 | try: 90 | self.reviewed_item = self.ctype.get_object_for_this_type( 91 | pk=kwargs.get('object_id')) 92 | except ObjectDoesNotExist: 93 | raise Http404 94 | 95 | # Check for permission 96 | if request.user.is_authenticated(): 97 | # Check, if user has already reviewed this item 98 | if getattr(settings, 'REVIEW_AVOID_MULTIPLE_REVIEWS', False): 99 | try: 100 | old_review = Review.objects.filter( 101 | user=request.user, content_type=self.ctype, 102 | object_id=kwargs.get('object_id'))[0] 103 | except IndexError: 104 | pass 105 | else: 106 | return HttpResponseRedirect( 107 | reverse('review_update', kwargs={'pk': old_review.pk})) 108 | # Check the custom permission function 109 | has_perm = getattr(settings, 'REVIEW_PERMISSION_FUNCTION', None) 110 | if (callable(has_perm) and not has_perm( 111 | request.user, self.reviewed_item)): 112 | raise Http404 113 | return super(ReviewCreateView, self).dispatch(request, *args, **kwargs) 114 | 115 | 116 | class ReviewDetailView(DetailView): 117 | """View to display a ``Review`` instance.""" 118 | model = Review 119 | 120 | 121 | class ReviewUpdateView(ReviewViewMixin, ReviewUpdateMixin, UpdateView): 122 | """View to update a ``Review`` instance.""" 123 | pass 124 | 125 | 126 | class ReviewDeleteView(ReviewViewMixin, ReviewUpdateMixin, DeleteView): 127 | """View to delete a ``Review`` instance.""" 128 | def get_success_url(self): 129 | if getattr(settings, 'REVIEW_DELETION_SUCCESS_URL', False): 130 | return reverse(settings.REVIEW_DELETION_SUCCESS_URL) 131 | return '/' 132 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | This script is used to run tests, create a coverage report and output the 4 | statistics at the end of the tox run. 5 | To run this script just execute ``tox`` 6 | """ 7 | import re 8 | 9 | from fabric.api import local, warn 10 | from fabric.colors import green, red 11 | 12 | 13 | if __name__ == '__main__': 14 | local('flake8 --ignore=E126 --ignore=W391 --statistics' 15 | ' --exclude=submodules,migrations,south_migrations,build,.tox .') 16 | local('coverage run --source="review" manage.py test -v 2' 17 | ' --traceback --failfast' 18 | ' --settings=review.tests.settings' 19 | ' --pattern="*_tests.py"') 20 | local('coverage html -d coverage --omit="*__init__*,*/settings/*,' 21 | '*/migrations/*,*/south_migrations/*,*/tests/*,*admin*"') 22 | total_line = local('grep -n pc_cov coverage/index.html', capture=True) 23 | percentage = float(re.findall(r'(\d+)%', total_line)[-1]) 24 | if percentage < 100: 25 | warn(red('Coverage is {0}%'.format(percentage))) 26 | print(green('Coverage is {0}%'.format(percentage))) 27 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Python setup file for the review app. 4 | 5 | In order to register your app at pypi.python.org, create an account at 6 | pypi.python.org and login, then register your new app like so: 7 | 8 | python setup.py register 9 | 10 | If your name is still free, you can now make your first release but first you 11 | should check if you are uploading the correct files: 12 | 13 | python setup.py sdist 14 | 15 | Inspect the output thoroughly. There shouldn't be any temp files and if your 16 | app includes staticfiles or templates, make sure that they appear in the list. 17 | If something is wrong, you need to edit MANIFEST.in and run the command again. 18 | 19 | If all looks good, you can make your first release: 20 | 21 | python setup.py sdist upload 22 | 23 | For new releases, you need to bump the version number in 24 | review/__init__.py and re-run the above command. 25 | 26 | For more information on creating source distributions, see 27 | http://docs.python.org/2/distutils/sourcedist.html 28 | 29 | """ 30 | import os 31 | from setuptools import setup, find_packages 32 | import review as app 33 | 34 | 35 | def read(fname): 36 | try: 37 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 38 | except IOError: 39 | return '' 40 | 41 | 42 | setup( 43 | name="django-review", 44 | version=app.__version__, 45 | description=read('DESCRIPTION'), 46 | long_description=read('README.rst'), 47 | license='The MIT License', 48 | platforms=['OS Independent'], 49 | keywords='django, app, reusable, review, rating, voting', 50 | author='Daniel Kaufhold', 51 | author_email='daniel.kaufhold@bitlabstudio.com', 52 | url="https://github.com/bitmazk/django-review", 53 | packages=find_packages(), 54 | include_package_data=True, 55 | classifiers=[ 56 | 'Development Status :: 5 - Production/Stable', 57 | 'Intended Audience :: Developers', 58 | 'Topic :: Internet :: WWW/HTTP', 59 | 'Topic :: Software Development :: Libraries :: Python Modules', 60 | 'License :: OSI Approved :: MIT License', 61 | 'Programming Language :: Python :: 2', 62 | 'Programming Language :: Python :: 2.7', 63 | 'Programming Language :: Python :: 3', 64 | 'Programming Language :: Python :: 3.5', 65 | ], 66 | install_requires=[ 67 | 'django', 68 | 'django-user-media>=1.2.1', 69 | 'django-generic-positions>=0.2', 70 | 'django-hvad', 71 | 'easy-thumbnails', 72 | 'simplejson', 73 | ], 74 | ) 75 | -------------------------------------------------------------------------------- /test_requirements.txt: -------------------------------------------------------------------------------- 1 | fabric3 2 | mixer 3 | mock 4 | coverage 5 | django-coverage 6 | ipdb 7 | flake8 8 | tox 9 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27-django{18,19,110},py35-django{19,110} 3 | 4 | [testenv] 5 | usedevelop = True 6 | deps = 7 | django18: Django>=1.8,<1.9 8 | django19: Django>=1.9,<1.10 9 | django110: Django>=1.10,<1.11 10 | -rtest_requirements.txt 11 | commands = python runtests.py 12 | --------------------------------------------------------------------------------