Browse Source

fix #475: misago.core.utils.slugify is swapable now

Rafał Pitoń 8 years ago
parent
commit
140ea978aa
4 changed files with 102 additions and 42 deletions
  1. 42 37
      docs/settings/Core.md
  2. 10 0
      misago/core/slugify.py
  3. 30 1
      misago/core/tests/test_utils.py
  4. 20 4
      misago/core/utils.py

+ 42 - 37
docs/settings/Core.md

@@ -4,39 +4,39 @@ Core Settings
 Those settings are set in `settings.py` file with defaults defined in `misago.conf.defaults` module. 
 Those settings are set in `settings.py` file with defaults defined in `misago.conf.defaults` module. 
 
 
 
 
-## MISAGO_403_IMAGE
+## `MISAGO_403_IMAGE`
 
 
 Url (relative to `STATIC_URL`) to file that should be served if user has no permission to see requested attachment.
 Url (relative to `STATIC_URL`) to file that should be served if user has no permission to see requested attachment.
 
 
 
 
-## MISAGO_404_IMAGE
+## `MISAGO_404_IMAGE`
 
 
 Url (relative to `STATIC_URL`) to file that should be served if user has requested nonexistant attachment.
 Url (relative to `STATIC_URL`) to file that should be served if user has requested nonexistant attachment.
 
 
 
 
-## MISAGO_ACL_EXTENSIONS
+## `MISAGO_ACL_EXTENSIONS`
 
 
 List of Misago ACL framework extensions.
 List of Misago ACL framework extensions.
 
 
 
 
-## MISAGO_ADMIN_NAMESPACES
+## `MISAGO_ADMIN_NAMESPACES`
 
 
 Link namespaces that are administrator-only areas that require additional security from Misago. Users will have to re-authenticate themselves to access those namespaces, even if they are already signed in your frontend. In addition they will be requested to reauthenticated if they were inactive in those namespaces for certain time.
 Link namespaces that are administrator-only areas that require additional security from Misago. Users will have to re-authenticate themselves to access those namespaces, even if they are already signed in your frontend. In addition they will be requested to reauthenticated if they were inactive in those namespaces for certain time.
 
 
 Defautly `misago:admin` and `admin` namespaces are specified, putting both Misago and Django default admin interfaces under extended security mechanics.
 Defautly `misago:admin` and `admin` namespaces are specified, putting both Misago and Django default admin interfaces under extended security mechanics.
 
 
 
 
-## MISAGO_ADMIN_PATH
+## `MISAGO_ADMIN_PATH`
 
 
 Path prefix for Misago administration backend. Defautly "admincp", but you may set it to empty string if you with to disable your forum administration backend.
 Path prefix for Misago administration backend. Defautly "admincp", but you may set it to empty string if you with to disable your forum administration backend.
 
 
 
 
-## MISAGO_ADMIN_SESSION_EXPIRATION
+## `MISAGO_ADMIN_SESSION_EXPIRATION`
 
 
 Maximum allowed lenght of inactivity period between two requests to admin namespaces. If its exceeded, user will be asked to sign in again to admin backed before being allowed to continue activities.
 Maximum allowed lenght of inactivity period between two requests to admin namespaces. If its exceeded, user will be asked to sign in again to admin backed before being allowed to continue activities.
 
 
 
 
-## MISAGO_ATTACHMENT_IMAGE_SIZE_LIMIT
+## `MISAGO_ATTACHMENT_IMAGE_SIZE_LIMIT`
 
 
 Max dimensions (width and height) of user-uploaded images embedded in posts. If uploaded image is greater than dimensions specified in this settings, Misago will generate thumbnail for it.
 Max dimensions (width and height) of user-uploaded images embedded in posts. If uploaded image is greater than dimensions specified in this settings, Misago will generate thumbnail for it.
 
 
@@ -46,12 +46,12 @@ Max dimensions (width and height) of user-uploaded images embedded in posts. If
 Because user-uploaded GIF's may be smaller than dimensions specified, but still be considerably heavy due to animation, Misago always generates thumbnails for user-uploaded GIFS, stripping the animations from them.
 Because user-uploaded GIF's may be smaller than dimensions specified, but still be considerably heavy due to animation, Misago always generates thumbnails for user-uploaded GIFS, stripping the animations from them.
 
 
 
 
-## MISAGO_ATTACHMENT_ORPHANED_EXPIRE
+## `MISAGO_ATTACHMENT_ORPHANED_EXPIRE`
 
 
 How old (in minutes) should attachments unassociated with any be before they'll automatically deleted by `clearattachments` task.
 How old (in minutes) should attachments unassociated with any be before they'll automatically deleted by `clearattachments` task.
 
 
 
 
-## MISAGO_ATTACHMENT_SECRET_LENGTH
+## `MISAGO_ATTACHMENT_SECRET_LENGTH`
 
 
 Length of attachment's secret (filenames and url token). The longer, the harder it is to bruteforce, but too long may conflict with your uploaded files storage limits (eg. filesystem path length limits).
 Length of attachment's secret (filenames and url token). The longer, the harder it is to bruteforce, but too long may conflict with your uploaded files storage limits (eg. filesystem path length limits).
 
 
@@ -63,7 +63,7 @@ In order for Misago to support clustered deployments or CDN's (like Amazon's S3)
 Generaly, neither you nor your users should use forums to exchange files containing valuable data, but if you do, you should make sure to secure it additionaly via other means like password-protected archives or file encryption solutions.
 Generaly, neither you nor your users should use forums to exchange files containing valuable data, but if you do, you should make sure to secure it additionaly via other means like password-protected archives or file encryption solutions.
 
 
 
 
-## MISAGO_AVATAR_GALLERY
+## `MISAGO_AVATAR_GALLERY`
 
 
 Path to directory containing avatar galleries. Those galleries can be loaded by running `loadavatargallery` command.
 Path to directory containing avatar galleries. Those galleries can be loaded by running `loadavatargallery` command.
 
 
@@ -72,7 +72,7 @@ Feel free to remove existing galleries or add your own.
 If you create gallery named `__default__` and set avatar gallery as default user avatar, Misago will select new users avatars from it while keeping this gallery hidden from existing users.
 If you create gallery named `__default__` and set avatar gallery as default user avatar, Misago will select new users avatars from it while keeping this gallery hidden from existing users.
 
 
 
 
-## MISAGO_AVATARS_SIZES
+## `MISAGO_AVATARS_SIZES`
 
 
 Misago uses avatar cache that prescales avatars to requested sizes. Enter here sizes to which those should be optimized.
 Misago uses avatar cache that prescales avatars to requested sizes. Enter here sizes to which those should be optimized.
 
 
@@ -82,103 +82,103 @@ Misago uses avatar cache that prescales avatars to requested sizes. Enter here s
 It's impossible to regenerate user avatars store for existing avatars. Misago comes with sane defaults for avatar sizes, with min. height for user avatar being 400 pixels square, and steps of 200, 150, 100, 64, 50 and 30px. However if you need larger avatar or different pregenerated dimensions, changing those will require you to manually remove `avatars` directory from your media storage as well as running `misago.users.avatars.set_default_avatar` function against every user registered.
 It's impossible to regenerate user avatars store for existing avatars. Misago comes with sane defaults for avatar sizes, with min. height for user avatar being 400 pixels square, and steps of 200, 150, 100, 64, 50 and 30px. However if you need larger avatar or different pregenerated dimensions, changing those will require you to manually remove `avatars` directory from your media storage as well as running `misago.users.avatars.set_default_avatar` function against every user registered.
 
 
 
 
-## MISAGO_BLANK_AVATAR
+## `MISAGO_BLANK_AVATAR`
 
 
 This path to image file that Misago should use as blank avatar.
 This path to image file that Misago should use as blank avatar.
 
 
 
 
-## MISAGO_COMPACT_DATE_FORMAT_DAY_MONTH
+## `MISAGO_COMPACT_DATE_FORMAT_DAY_MONTH`
 
 
 Date format used by Misago `compact_date` filter for dates in this year.
 Date format used by Misago `compact_date` filter for dates in this year.
 
 
 Expects standard Django date format, documented [here](https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date)
 Expects standard Django date format, documented [here](https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date)
 
 
 
 
-## MISAGO_COMPACT_DATE_FORMAT_DAY_MONTH_YEAR
+## `MISAGO_COMPACT_DATE_FORMAT_DAY_MONTH_YEAR`
 
 
 Date format used by Misago `compact_date` filter for dates in past years.
 Date format used by Misago `compact_date` filter for dates in past years.
 
 
 Expects standard Django date format, documented [here](https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date)
 Expects standard Django date format, documented [here](https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date)
 
 
 
 
-## MISAGO_DIALY_POST_LIMIT
+## `MISAGO_DIALY_POST_LIMIT`
 
 
 Dialy limit of posts that may be posted from single account. Fail-safe for situations when forum is flooded by spam bot. Change to 0 to lift this restriction.
 Dialy limit of posts that may be posted from single account. Fail-safe for situations when forum is flooded by spam bot. Change to 0 to lift this restriction.
 
 
 
 
-## MISAGO_DYNAMIC_AVATAR_DRAWER
+## `MISAGO_DYNAMIC_AVATAR_DRAWER`
 
 
 Function used to create unique avatar for this user. Allows for customization of algorithm used to generate those.
 Function used to create unique avatar for this user. Allows for customization of algorithm used to generate those.
 
 
 
 
-## MISAGO_EVENTS_PER_PAGE
+## `MISAGO_EVENTS_PER_PAGE`
 
 
 Misago reads events to display in separate database query to avoid situation when thread with large number of eg. moderator actions displays pages consisting exclusively of events. Using this setting you may specify upper limit of events displayed on thread's single page. This setting is intented as fail safe, both to save threads from excessively long lists of events your users will have to scroll trough, as well as to keep memory usage within limts.
 Misago reads events to display in separate database query to avoid situation when thread with large number of eg. moderator actions displays pages consisting exclusively of events. Using this setting you may specify upper limit of events displayed on thread's single page. This setting is intented as fail safe, both to save threads from excessively long lists of events your users will have to scroll trough, as well as to keep memory usage within limts.
 
 
 In case of more events than specified being found, oldest events will be truncated.
 In case of more events than specified being found, oldest events will be truncated.
 
 
 
 
-## MISAGO_HOURLY_POST_LIMIT
+## `MISAGO_HOURLY_POST_LIMIT`
 
 
 Hourly limit of posts that may be posted from single account. Fail-safe for situations when forum is flooded by spam bot. Change to 0 to lift this restriction.
 Hourly limit of posts that may be posted from single account. Fail-safe for situations when forum is flooded by spam bot. Change to 0 to lift this restriction.
 
 
 
 
-## MISAGO_LOGIN_API_URL
+## `MISAGO_LOGIN_API_URL`
 URL to API endpoint used to authenticate sign-in credentials. Musn't contain api prefix or wrapping slashes. Defaults to 'auth/login'.
 URL to API endpoint used to authenticate sign-in credentials. Musn't contain api prefix or wrapping slashes. Defaults to 'auth/login'.
 
 
 
 
-## MISAGO_MARKUP_EXTENSIONS
+## `MISAGO_MARKUP_EXTENSIONS`
 
 
 List of python modules extending Misago markup.
 List of python modules extending Misago markup.
 
 
 
 
-## MISAGO_NEW_REGISTRATIONS_VALIDATORS
+## `MISAGO_NEW_REGISTRATIONS_VALIDATORS`
 
 
 List of functions to be called when somebody attempts to register on forums using registration form.
 List of functions to be called when somebody attempts to register on forums using registration form.
 
 
 
 
-## MISAGO_NOTIFICATIONS_MAX_AGE
+## `MISAGO_NOTIFICATIONS_MAX_AGE`
 
 
 Max age, in days, of notifications stored in database. Notifications older than this will be delted.
 Max age, in days, of notifications stored in database. Notifications older than this will be delted.
 
 
 
 
-## MISAGO_POST_ATTACHMENTS_LIMIT
+## `MISAGO_POST_ATTACHMENTS_LIMIT`
 
 
 Limit of attachments that may be uploaded in single post. Lower limits may hamper image-heavy forums, but help keep memory usage by posting process. 
 Limit of attachments that may be uploaded in single post. Lower limits may hamper image-heavy forums, but help keep memory usage by posting process. 
 
 
 
 
-## MISAGO_POSTING_MIDDLEWARES
+## `MISAGO_POSTING_MIDDLEWARES`
 
 
 List of middleware classes participating in posting process.
 List of middleware classes participating in posting process.
 
 
 
 
-## MISAGO_POSTS_PER_PAGE
+## `MISAGO_POSTS_PER_PAGE`
 
 
 Controls number of posts displayed on thread page. Greater numbers can increase number of objects loaded into memory and thus depending on features enabled greatly increase memory usage.
 Controls number of posts displayed on thread page. Greater numbers can increase number of objects loaded into memory and thus depending on features enabled greatly increase memory usage.
 
 
 
 
-## MISAGO_POSTS_TAIL
+## `MISAGO_POSTS_TAIL`
 
 
 Defines minimal number of posts for thread's last page. If number of posts on last page is smaller or equal to one specified in this setting, last page will be appended to previous page instead.
 Defines minimal number of posts for thread's last page. If number of posts on last page is smaller or equal to one specified in this setting, last page will be appended to previous page instead.
 
 
 
 
-## MISAGO_RANKING_LENGTH
+## `MISAGO_RANKING_LENGTH`
 
 
 Some lists act as rankings, displaying users in order of certain scoring criteria, like number of posts or likes received.
 Some lists act as rankings, displaying users in order of certain scoring criteria, like number of posts or likes received.
 This setting controls maximum age in days of items that should count to ranking.
 This setting controls maximum age in days of items that should count to ranking.
 
 
 
 
-## MISAGO_RANKING_SIZE
+## `MISAGO_RANKING_SIZE`
 
 
 Maximum number of items on ranking page.
 Maximum number of items on ranking page.
 
 
 
 
-## MISAGO_READTRACKER_CUTOFF
+## `MISAGO_READTRACKER_CUTOFF`
 
 
 Controls amount of data used by readtracking system. All content older than number of days specified in this setting is considered old and read, even if opposite is true. Active forums can try lowering this value while less active ones may wish to increase it instead.
 Controls amount of data used by readtracking system. All content older than number of days specified in this setting is considered old and read, even if opposite is true. Active forums can try lowering this value while less active ones may wish to increase it instead.
 
 
 
 
-## MISAGO_SEARCH_CONFIG
+## `MISAGO_SEARCH_CONFIG`
 
 
 PostgreSQL text search configuration to use in searches. Defaults to "simple", for list of installed configurations run "\dF" in "psql".
 PostgreSQL text search configuration to use in searches. Defaults to "simple", for list of installed configurations run "\dF" in "psql".
 
 
@@ -192,36 +192,41 @@ Example on adding custom language can be found [here](https://github.com/lemonsk
 Items in Misago are usually indexed in search engine on save or update. If you change search configuration, you'll need to rebuild search for past posts to get reindexed using new configuration. Misago comes with `rebuildpostssearch` tool for this purpose.
 Items in Misago are usually indexed in search engine on save or update. If you change search configuration, you'll need to rebuild search for past posts to get reindexed using new configuration. Misago comes with `rebuildpostssearch` tool for this purpose.
 
 
 
 
-## MISAGO_STOP_FORUM_SPAM_MIN_CONFIDENCE
+## `MISAGO_SLUGIFY`
+
+Path to function or callable used by Misago to generate slugs. Defaults to `misago.core.slugify.default`. Use this function if you want to customize slugs generation on your community.
+
+
+## `MISAGO_STOP_FORUM_SPAM_MIN_CONFIDENCE`
 
 
 Minimum confidence returned by [Stop Forum Spam](http://www.stopforumspam.com/) for Misago to reject new registration and block IP address for 1 day.
 Minimum confidence returned by [Stop Forum Spam](http://www.stopforumspam.com/) for Misago to reject new registration and block IP address for 1 day.
 
 
 
 
-## MISAGO_THREADS_ON_INDEX
+## `MISAGO_THREADS_ON_INDEX`
 
 
 Change this setting to `False` to display categories list instead of threads list on board index.
 Change this setting to `False` to display categories list instead of threads list on board index.
 
 
 
 
-## MISAGO_THREADS_PER_PAGE
+## `MISAGO_THREADS_PER_PAGE`
 
 
 Controls number of threads displayed on page. Greater numbers can increase number of objects loaded into memory and thus depending on features enabled greatly increase memory usage.
 Controls number of threads displayed on page. Greater numbers can increase number of objects loaded into memory and thus depending on features enabled greatly increase memory usage.
 
 
 
 
-## MISAGO_THREADS_TAIL
+## `MISAGO_THREADS_TAIL`
 
 
 Defines minimal number of threads for lists last page. If number of threads on last page is smaller or equal to one specified in this setting, last page will be appended to previous page instead.
 Defines minimal number of threads for lists last page. If number of threads on last page is smaller or equal to one specified in this setting, last page will be appended to previous page instead.
 
 
 
 
-## MISAGO_THREAD_TYPES
+## `MISAGO_THREAD_TYPES`
 
 
 List of clasess defining thread types.
 List of clasess defining thread types.
 
 
 
 
-## MISAGO_USE_STOP_FORUM_SPAM
+## `MISAGO_USE_STOP_FORUM_SPAM`
 
 
 This settings allows you to decide wheter of not [Stop Forum Spam](http://www.stopforumspam.com/)database should be used to validate IPs and emails during new users registrations.
 This settings allows you to decide wheter of not [Stop Forum Spam](http://www.stopforumspam.com/)database should be used to validate IPs and emails during new users registrations.
 
 
 
 
-## MISAGO_USERS_PER_PAGE
+## `MISAGO_USERS_PER_PAGE`
 
 
 Controls pagination of users lists.
 Controls pagination of users lists.

+ 10 - 0
misago/core/slugify.py

@@ -0,0 +1,10 @@
+from unidecode import unidecode
+
+from django.template.defaultfilters import slugify as django_slugify
+from django.utils import six
+
+
+def default(string):
+    string = six.text_type(string)
+    string = unidecode(string)
+    return django_slugify(string.replace('_', ' ').strip())

+ 30 - 1
misago/core/tests/test_utils.py

@@ -3,7 +3,7 @@ from __future__ import unicode_literals
 from django.test import TestCase
 from django.test import TestCase
 from django.test.client import RequestFactory
 from django.test.client import RequestFactory
 from django.urls import reverse
 from django.urls import reverse
-from django.utils import timezone
+from django.utils import six, timezone
 
 
 from ..utils import (
 from ..utils import (
     clean_return_path,
     clean_return_path,
@@ -11,6 +11,7 @@ from ..utils import (
     is_referer_local,
     is_referer_local,
     is_request_to_misago,
     is_request_to_misago,
     parse_iso8601_string,
     parse_iso8601_string,
+    resolve_slugify,
     slugify
     slugify
 )
 )
 
 
@@ -49,6 +50,34 @@ class IsRequestToMisagoTests(TestCase):
 
 
 
 
 class SlugifyTests(TestCase):
 class SlugifyTests(TestCase):
+    def test_resolve_invalid_module(self):
+        """resolve_slugify raises import error for invalid module"""
+        with self.assertRaises(ImportError):
+            resolve_slugify('some.invalid.path')
+
+        try:
+            resolve_slugify('some.invalid.path')
+        except ImportError as e:
+            error_message = six.text_type(e)
+            self.assertEqual(error_message, 'module some.invalid does not exist')
+
+    def test_resolve_nonexistant_name(self):
+        """resolve_slugify raises import error for invalid name"""
+        with self.assertRaises(ImportError):
+            resolve_slugify('misago.threads.invalidname')
+
+        try:
+            resolve_slugify('misago.threads.invalidname')
+        except ImportError as e:
+            error_message = six.text_type(e)
+            self.assertEqual(
+                error_message, 'name invalidname not found in misago.threads module')
+
+    def test_resolve_valid_name(self):
+        """resolve_slugify resolves valid paths"""
+        resolved_slugify = resolve_slugify('misago.core.slugify.default')
+        self.assertEqual(resolved_slugify, slugify)
+
     def test_valid_slugify_output(self):
     def test_valid_slugify_output(self):
         """Misago's slugify correctly slugifies string"""
         """Misago's slugify correctly slugifies string"""
         test_cases = (
         test_cases = (

+ 20 - 4
misago/core/utils.py

@@ -1,10 +1,8 @@
 from datetime import datetime, timedelta
 from datetime import datetime, timedelta
+from importlib import import_module
 
 
-import six
-from unidecode import unidecode
-
+from django.conf import settings
 from django.http import Http404
 from django.http import Http404
-from django.template.defaultfilters import slugify as django_slugify
 from django.urls import resolve, reverse
 from django.urls import resolve, reverse
 from django.utils import html, timezone
 from django.utils import html, timezone
 from django.utils.encoding import force_text
 from django.utils.encoding import force_text
@@ -12,12 +10,30 @@ from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ungettext_lazy
 from django.utils.translation import ungettext_lazy
 
 
 
 
+MISAGO_SLUGIFY = getattr(settings, 'MISAGO_SLUGIFY', 'misago.core.slugify.default')
+
+
 def slugify(string):
 def slugify(string):
     string = six.text_type(string)
     string = six.text_type(string)
     string = unidecode(string)
     string = unidecode(string)
     return django_slugify(string.replace('_', ' ').strip())
     return django_slugify(string.replace('_', ' ').strip())
 
 
 
 
+def resolve_slugify(path):
+    path_bits = path.split('.')
+    module, name = '.'.join(path_bits[:-1]), path_bits[-1]
+
+    try:
+        return getattr(import_module(module), name)
+    except AttributeError:
+        raise ImportError("name {} not found in {} module".format(name, module))
+    except ImportError:
+        raise ImportError("module {} does not exist".format(module))
+
+
+slugify = resolve_slugify(MISAGO_SLUGIFY)
+
+
 def format_plaintext_for_html(string):
 def format_plaintext_for_html(string):
     return html.linebreaks(html.urlize(html.escape(string)))
     return html.linebreaks(html.urlize(html.escape(string)))