Passed
Push — master ( 5a4f44...92a53b )
by Swen
01:57
created

LocalizedAutoSlugField._get_populate_values()   A

Complexity

Conditions 2

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
c 0
b 0
f 0
dl 0
loc 22
ccs 2
cts 2
cp 1
crap 2
rs 9.2
1 1
from typing import Callable, Tuple
0 ignored issues
show
Configuration introduced by
The import typing could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
2 1
from datetime import datetime
3
4 1
from django import forms
0 ignored issues
show
Configuration introduced by
The import django could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
5 1
from django.conf import settings
0 ignored issues
show
Configuration introduced by
The import django.conf could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
6 1
from django.utils.text import slugify
0 ignored issues
show
Configuration introduced by
The import django.utils.text could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
7
8 1
from .field import LocalizedField
9 1
from ..value import LocalizedValue
10
11
12 1
class LocalizedAutoSlugField(LocalizedField):
13
    """Automatically provides slugs for a localized
14
    field upon saving."""
15
16 1
    def __init__(self, *args, **kwargs):
17
        """Initializes a new instance of :see:LocalizedAutoSlugField."""
18
19 1
        self.populate_from = kwargs.pop('populate_from', None)
20 1
        self.include_time = kwargs.pop('include_time', False)
21
22 1
        super(LocalizedAutoSlugField, self).__init__(
23
            *args,
24
            **kwargs
25
        )
26
27 1
    def deconstruct(self):
28
        """Deconstructs the field into something the database
29
        can store."""
30
31 1
        name, path, args, kwargs = super(
32
            LocalizedAutoSlugField, self).deconstruct()
33
34 1
        kwargs['populate_from'] = self.populate_from
35 1
        kwargs['include_time'] = self.include_time
36 1
        return name, path, args, kwargs
37
38 1
    def formfield(self, **kwargs):
39
        """Gets the form field associated with this field.
40
41
        Because this is a slug field which is automatically
42
        populated, it should be hidden from the form.
43
        """
44
45 1
        defaults = {
46
            'form_class': forms.CharField,
47
            'required': False
48
        }
49
50 1
        defaults.update(kwargs)
51
52 1
        form_field = super().formfield(**defaults)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
53 1
        form_field.widget = forms.HiddenInput()
54
55 1
        return form_field
56
57 1
    def pre_save(self, instance, add: bool):
0 ignored issues
show
Unused Code introduced by
The argument add seems to be unused.
Loading history...
58
        """Ran just before the model is saved, allows us to built
59
        the slug.
60
61
        Arguments:
62
            instance:
63
                The model that is being saved.
64
65
            add:
66
                Indicates whether this is a new entry
67
                to the database or an update.
68
        """
69
70 1
        slugs = LocalizedValue()
71
72 1
        for lang_code, value in self._get_populate_values(instance):
73 1
            if not value:
74 1
                continue
75
76 1
            if self.include_time:
77
                value += '-%s' % datetime.now().microsecond
78
79 1
            def is_unique(slug: str, language: str) -> bool:
80
                """Gets whether the specified slug is unique."""
81
82 1
                unique_filter = {
83
                    '%s__%s' % (self.name, language): slug
0 ignored issues
show
Bug introduced by
The Instance of LocalizedAutoSlugField does not seem to have a member named name.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
84
                }
85
86 1
                return not type(instance).objects.filter(**unique_filter).exists()
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
87
88 1
            slug = self._make_unique_slug(
89
                slugify(value, allow_unicode=True),
90
                lang_code,
91
                is_unique
92
            )
93
94 1
            slugs.set(lang_code, slug)
95
96 1
        setattr(instance, self.name, slugs)
0 ignored issues
show
Bug introduced by
The Instance of LocalizedAutoSlugField does not seem to have a member named name.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
97 1
        return slugs
98
99 1
    @staticmethod
100 1
    def _make_unique_slug(slug: str, language: str, is_unique: Callable[[str], bool]) -> str:
101
        """Guarentees that the specified slug is unique by appending
102
        a number until it is unique.
103
104
        Arguments:
105
            slug:
106
                The slug to make unique.
107
108
            is_unique:
109
                Function that can be called to verify
110
                whether the generate slug is unique.
111
112
        Returns:
113
            A guarenteed unique slug.
114
        """
115
116 1
        index = 1
117 1
        unique_slug = slug
118
119 1
        while not is_unique(unique_slug, language):
120 1
            unique_slug = '%s-%d' % (slug, index)
121 1
            index += 1
122
123 1
        return unique_slug
124
125 1
    def _get_populate_values(self, instance) -> Tuple[str, str]:
126
        """Gets all values (for each language) from the
127
        specified's instance's `populate_from` field.
128
129
        Arguments:
130
            instance:
131
                The instance to get the values from.
132
133
        Returns:
134
            A list of (lang_code, value) tuples.
135
        """
136
137 1
        return [
138
            (
139
                lang_code,
140
                self._get_populate_from_value(
141
                    instance,
142
                    self.populate_from,
143
                    lang_code
144
                ),
145
            )
146
            for lang_code, _ in settings.LANGUAGES
147
        ]
148
149 1
    @staticmethod
150 1
    def _get_populate_from_value(instance, field_name: str, language: str):
151
        """Gets the value to create a slug from in the specified language.
152
153
        Arguments:
154
            instance:
155
                The model that the field resides on.
156
157
            field_name:
158
                The name of the field to generate a slug for.
159
160
            language:
161
                The language to generate the slug for.
162
163
        Returns:
164
            The text to generate a slug for.
165
        """
166
167 1
        value = getattr(instance, field_name, None)
168
        return value.get(language)
169