django-rubble 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_rubble/__init__.py +0 -0
- django_rubble/admin/__init__.py +0 -0
- django_rubble/admin/stamped_admin.py +12 -0
- django_rubble/apps.py +7 -0
- django_rubble/fields/__init__.py +0 -0
- django_rubble/fields/auto_fields.py +30 -0
- django_rubble/fields/db_fields.py +52 -0
- django_rubble/forms/__init__.py +0 -0
- django_rubble/forms/db_forms.py +57 -0
- django_rubble/migrations/__init__.py +0 -0
- django_rubble/models/__init__.py +0 -0
- django_rubble/models/stamped_models.py +22 -0
- django_rubble/tests.py +0 -0
- django_rubble/utils/__init__.py +0 -0
- django_rubble/utils/default_funcs.py +8 -0
- django_rubble/utils/numbers.py +102 -0
- django_rubble/utils/strings.py +8 -0
- django_rubble/views/__init__.py +0 -0
- django_rubble/views/create_views.py +19 -0
- django_rubble-0.2.0.dist-info/LICENSE +21 -0
- django_rubble-0.2.0.dist-info/METADATA +63 -0
- django_rubble-0.2.0.dist-info/RECORD +23 -0
- django_rubble-0.2.0.dist-info/WHEEL +4 -0
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from django.contrib import admin # type: ignore[import-untyped]
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class StampedAdmin(admin.ModelAdmin):
|
|
5
|
+
"""A ModelAdmin that sets the created_by and modified_by fields of a StampedModel
|
|
6
|
+
instance to the user that created or modified the instance."""
|
|
7
|
+
|
|
8
|
+
def save_model(self, request, obj, form, change):
|
|
9
|
+
if not change:
|
|
10
|
+
obj.created_by = request.user
|
|
11
|
+
obj.modified_by = request.user
|
|
12
|
+
super().save_model(request, obj, form, change)
|
django_rubble/apps.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
from django.db import models
|
|
3
|
+
|
|
4
|
+
AUTH_USER_MODEL = getattr(settings, "AUTH_USER_MODEL", "auth.User")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AutoCreatedByField(models.ForeignKey):
|
|
8
|
+
"""A ForeignKey to the user model that is not editable and has a related name
|
|
9
|
+
of '+'.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, *args, **kwargs):
|
|
13
|
+
kwargs.setdefault("editable", False)
|
|
14
|
+
kwargs.setdefault("related_name", "+")
|
|
15
|
+
kwargs.setdefault("to", AUTH_USER_MODEL)
|
|
16
|
+
kwargs.setdefault("on_delete", models.PROTECT)
|
|
17
|
+
super().__init__(*args, **kwargs)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AutoModifiedByField(models.ForeignKey):
|
|
21
|
+
"""A ForeignKey to the user model that is not editable and has a related name
|
|
22
|
+
of '+'.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, *args, **kwargs):
|
|
26
|
+
kwargs.setdefault("editable", False)
|
|
27
|
+
kwargs.setdefault("related_name", "+")
|
|
28
|
+
kwargs.setdefault("to", AUTH_USER_MODEL)
|
|
29
|
+
kwargs.setdefault("on_delete", models.PROTECT)
|
|
30
|
+
super().__init__(*args, **kwargs)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from django.utils.translation import gettext_lazy as _
|
|
3
|
+
|
|
4
|
+
from django_rubble.forms.db_forms import (
|
|
5
|
+
SimplePercentageField as SimplePercentageFormField,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SimplePercentageField(models.DecimalField):
|
|
10
|
+
"""Enter and display percentages out of 100 but store them out of 1
|
|
11
|
+
in db as decimals
|
|
12
|
+
|
|
13
|
+
Because this is based on `models.DecimalField`, `decimal_places` applies to what is
|
|
14
|
+
stored in the db (/1), not what is shown or typed in (/100). With that said, add two
|
|
15
|
+
(2) to whatever is desired in the form for proper validation.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
description = _(
|
|
19
|
+
"percentage (max {max_digits} digits; {decimal_places} decimal places"
|
|
20
|
+
)
|
|
21
|
+
log_name = "models.PercentageField"
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
verbose_name=None,
|
|
26
|
+
name=None,
|
|
27
|
+
max_digits=None,
|
|
28
|
+
decimal_places=None,
|
|
29
|
+
**kwargs,
|
|
30
|
+
):
|
|
31
|
+
if decimal_places is not None:
|
|
32
|
+
self.humanize_decimal_places = int(decimal_places) - 2
|
|
33
|
+
else:
|
|
34
|
+
self.humanize_decimal_places = None
|
|
35
|
+
|
|
36
|
+
kwargs.update(
|
|
37
|
+
{
|
|
38
|
+
"max_digits": max_digits,
|
|
39
|
+
"decimal_places": decimal_places,
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
super().__init__(verbose_name, name, **kwargs)
|
|
44
|
+
|
|
45
|
+
def formfield(self, **kwargs):
|
|
46
|
+
defaults = {"form_class": SimplePercentageFormField}
|
|
47
|
+
kwargs.update(
|
|
48
|
+
decimal_places=self.decimal_places - 2,
|
|
49
|
+
)
|
|
50
|
+
defaults.update(kwargs)
|
|
51
|
+
|
|
52
|
+
return super().formfield(**defaults)
|
|
File without changes
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from django import forms
|
|
2
|
+
from django.core.exceptions import ValidationError
|
|
3
|
+
from django.utils.translation import gettext_lazy as _
|
|
4
|
+
|
|
5
|
+
from django_rubble.utils.numbers import Percent, is_number
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SimplePercentageField(forms.DecimalField):
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
*,
|
|
12
|
+
max_value=None,
|
|
13
|
+
min_value=None,
|
|
14
|
+
max_digits=None,
|
|
15
|
+
decimal_places=None,
|
|
16
|
+
**kwargs,
|
|
17
|
+
):
|
|
18
|
+
if "widget" not in kwargs:
|
|
19
|
+
step = 10 ** (-1 * decimal_places)
|
|
20
|
+
kwargs["widget"] = forms.NumberInput(
|
|
21
|
+
attrs={"class": "percent", "step": step}
|
|
22
|
+
)
|
|
23
|
+
self.max_digits, self.decimal_places = max_digits, decimal_places
|
|
24
|
+
super().__init__(max_value=max_value, min_value=min_value, **kwargs)
|
|
25
|
+
|
|
26
|
+
def to_python(self, value):
|
|
27
|
+
val = super().to_python(value)
|
|
28
|
+
|
|
29
|
+
if isinstance(val, Percent):
|
|
30
|
+
return val.value
|
|
31
|
+
|
|
32
|
+
if is_number(val):
|
|
33
|
+
new_val = Percent.fromform(val)
|
|
34
|
+
|
|
35
|
+
return new_val.value
|
|
36
|
+
|
|
37
|
+
rtype = type(val)
|
|
38
|
+
raise ValidationError(
|
|
39
|
+
_("Invalid value type: %(rtype)s"),
|
|
40
|
+
code="invalid",
|
|
41
|
+
params={"rtype": rtype},
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def prepare_value(self, value):
|
|
45
|
+
val = super().prepare_value(value)
|
|
46
|
+
|
|
47
|
+
if isinstance(val, Percent):
|
|
48
|
+
return val.per_hundred
|
|
49
|
+
if is_number(val):
|
|
50
|
+
if isinstance(val, str):
|
|
51
|
+
new_val = Percent.fromform(val)
|
|
52
|
+
|
|
53
|
+
return new_val.per_hundred
|
|
54
|
+
|
|
55
|
+
return Percent(val).per_hundred
|
|
56
|
+
|
|
57
|
+
return val
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from model_utils.models import TimeStampedModel # type: ignore[import-untyped]
|
|
2
|
+
|
|
3
|
+
from django_rubble.fields.auto_fields import AutoCreatedByField, AutoModifiedByField
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class StampedModel(TimeStampedModel):
|
|
7
|
+
"""A model that has created_by and modified_by fields that are automatically
|
|
8
|
+
set to the user that created or modified the instance."""
|
|
9
|
+
|
|
10
|
+
created_by = AutoCreatedByField()
|
|
11
|
+
modified_by = AutoModifiedByField()
|
|
12
|
+
|
|
13
|
+
class Meta:
|
|
14
|
+
abstract = True
|
|
15
|
+
|
|
16
|
+
def save(self, *args, **kwargs):
|
|
17
|
+
update_fields = kwargs.get("update_fields", None)
|
|
18
|
+
if update_fields is not None:
|
|
19
|
+
if "modified_by" not in update_fields:
|
|
20
|
+
kwargs["update_fields"] = set(update_fields) | {"modified_by"}
|
|
21
|
+
|
|
22
|
+
return super().save(*args, **kwargs)
|
django_rubble/tests.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def is_number(s: Any):
|
|
8
|
+
"""Check if a value can be coerced into a number type."""
|
|
9
|
+
if s is None:
|
|
10
|
+
return False
|
|
11
|
+
try:
|
|
12
|
+
float(s)
|
|
13
|
+
except ValueError:
|
|
14
|
+
return False
|
|
15
|
+
else:
|
|
16
|
+
return True
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def ratio_to_whole(ratio: Decimal | float | str) -> Decimal:
|
|
20
|
+
return Decimal(str(ratio)) * Decimal("100")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def whole_to_ratio(whole: Decimal | float | str) -> Decimal:
|
|
24
|
+
return Decimal(str(whole)) * Decimal("0.01")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def trim_trailing_zeros(value: float | Decimal | str) -> Decimal:
|
|
28
|
+
"""Remove trailing zeros from a decimal value."""
|
|
29
|
+
if isinstance(value, Decimal):
|
|
30
|
+
_, _, exponent = value.as_tuple()
|
|
31
|
+
else:
|
|
32
|
+
_, _, exponent = Decimal(str(value)).as_tuple()
|
|
33
|
+
|
|
34
|
+
if not isinstance(exponent, int):
|
|
35
|
+
msg = "Exponent must be an integer"
|
|
36
|
+
raise TypeError(msg)
|
|
37
|
+
|
|
38
|
+
if exponent < 0:
|
|
39
|
+
new_str = str(value).rstrip("0")
|
|
40
|
+
return Decimal(new_str)
|
|
41
|
+
|
|
42
|
+
return Decimal(value)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def set_zero(value: float | Decimal | str) -> Decimal:
|
|
46
|
+
"""Set a value to a true Decimal zero if it is zero."""
|
|
47
|
+
decimal_from_string = Decimal(str(value))
|
|
48
|
+
|
|
49
|
+
_, decimal_digits, _ = decimal_from_string.as_tuple()
|
|
50
|
+
|
|
51
|
+
if len(decimal_digits) == 1 and decimal_digits[0] == 0:
|
|
52
|
+
return Decimal()
|
|
53
|
+
|
|
54
|
+
return decimal_from_string
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Percent(BaseModel):
|
|
58
|
+
value: Decimal | float | str
|
|
59
|
+
per_hundred: Decimal | float | str | None = None
|
|
60
|
+
decimal_places: int | None = None
|
|
61
|
+
has_decimal_places: bool | None = None
|
|
62
|
+
|
|
63
|
+
def model_post_init(self, __context: Any) -> None:
|
|
64
|
+
new_value = trim_trailing_zeros(self.value)
|
|
65
|
+
per_hundred_dec = trim_trailing_zeros(ratio_to_whole(self.value))
|
|
66
|
+
|
|
67
|
+
if self.decimal_places is not None:
|
|
68
|
+
new_value = round(new_value, self.decimal_places + 2)
|
|
69
|
+
per_hundred_dec = round(per_hundred_dec, self.decimal_places)
|
|
70
|
+
self.has_decimal_places = True
|
|
71
|
+
else:
|
|
72
|
+
self.has_decimal_places = False
|
|
73
|
+
|
|
74
|
+
self.value = set_zero(new_value)
|
|
75
|
+
self.per_hundred = set_zero(per_hundred_dec)
|
|
76
|
+
|
|
77
|
+
super().model_post_init(__context)
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def fromform(cls, val: Decimal, field_decimal_places: int | None = None):
|
|
81
|
+
"""Create Percent from human-entry (out of 100)"""
|
|
82
|
+
ratio_decimal = whole_to_ratio(val)
|
|
83
|
+
return cls(value=ratio_decimal, decimal_places=field_decimal_places)
|
|
84
|
+
|
|
85
|
+
def __mul__(self, other):
|
|
86
|
+
"""Multiply using the ratio (out of 1) instead of human-readable out of 100"""
|
|
87
|
+
return self.value.__mul__(other)
|
|
88
|
+
|
|
89
|
+
def __float__(self):
|
|
90
|
+
return float(self.value)
|
|
91
|
+
|
|
92
|
+
def as_tuple(self):
|
|
93
|
+
return self.value.as_tuple()
|
|
94
|
+
|
|
95
|
+
def is_finite(self):
|
|
96
|
+
return self.value.is_finite()
|
|
97
|
+
|
|
98
|
+
def __repr__(self) -> str:
|
|
99
|
+
return f"Percentage('{self.value}', '{self.per_hundred}%')"
|
|
100
|
+
|
|
101
|
+
def __str__(self):
|
|
102
|
+
return f"{self.per_hundred}%"
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
2
|
+
from django.views.generic import CreateView, UpdateView
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CreateStampedView(LoginRequiredMixin, CreateView):
|
|
6
|
+
"""A view that creates a StampedModel instance."""
|
|
7
|
+
|
|
8
|
+
def form_valid(self, form):
|
|
9
|
+
form.instance.created_by = self.request.user
|
|
10
|
+
form.instance.modified_by = self.request.user
|
|
11
|
+
return super().form_valid(form)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class UpdateStampedView(LoginRequiredMixin, UpdateView):
|
|
15
|
+
"""A view that updates a StampedModel instance."""
|
|
16
|
+
|
|
17
|
+
def form_valid(self, form):
|
|
18
|
+
form.instance.modified_by = self.request.user
|
|
19
|
+
return super().form_valid(form)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Wooster Technical Solutions
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: django-rubble
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Extend django-model-utils and django-extensions.
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Karl Wooster
|
|
7
|
+
Author-email: karl@woostertech.com
|
|
8
|
+
Requires-Python: >=3.10,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Dist: django (<=5.0)
|
|
15
|
+
Requires-Dist: django-model-utils (>=4.5.1,<5.0.0)
|
|
16
|
+
Requires-Dist: pydantic (>=2.7.1,<3.0.0)
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# django-rubble
|
|
20
|
+
|
|
21
|
+
[](https://opensource.org/licenses/MIT)
|
|
22
|
+
|
|
23
|
+
## Description
|
|
24
|
+
|
|
25
|
+
Extend [django-model-utils](https://github.com/jazzband/django-model-utils) and [django-extensions](https://github.com/django-extensions/django-extensions).
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- Feature 1
|
|
30
|
+
- Feature 2
|
|
31
|
+
- Feature 3
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
1. Clone the repository: `git clone https://github.com/WoosterTech/django-rubble.git`
|
|
36
|
+
2. Install the dependencies: `poetry install`
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
Explain how to use your Django project, including any important commands or
|
|
41
|
+
configurations.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
Contributions are welcome! Please follow the guidelines in
|
|
45
|
+
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
49
|
+
|
|
50
|
+
## Contact
|
|
51
|
+
|
|
52
|
+
- Author: Karl Wooster
|
|
53
|
+
- Email: <karl@woostertech.com>
|
|
54
|
+
- Website: [woostertech.com](https://woostertech.com)
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT (see [License](LICENSE))
|
|
59
|
+
|
|
60
|
+
## Contributing
|
|
61
|
+
|
|
62
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
63
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
django_rubble/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
django_rubble/admin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
django_rubble/admin/stamped_admin.py,sha256=db9nsMnARppGvMnGITUPc6JHNov1M28j6G7vFb8ge4s,484
|
|
4
|
+
django_rubble/apps.py,sha256=ufyuWOn9JvNKfztgkIUe5JPKo3lr_GSOJ1N9oi2-E6k,192
|
|
5
|
+
django_rubble/fields/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
django_rubble/fields/auto_fields.py,sha256=Ip2bZwucLa0f32vo1_mWw9ZGsUhRhrUJDqSfl297Eoo,1020
|
|
7
|
+
django_rubble/fields/db_fields.py,sha256=xfVRwMBCz-ahFBrEs5Nl_xug9hlaDBgeyO3QfKFybyM,1579
|
|
8
|
+
django_rubble/forms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
django_rubble/forms/db_forms.py,sha256=AUJ4NM__bvBHvna7FCVWS7TK9slgCcNxEWfAP8pfmcQ,1623
|
|
10
|
+
django_rubble/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
django_rubble/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
django_rubble/models/stamped_models.py,sha256=kVt0kGnwPE0YCwfefr4yjbU5CIxJV61XG1y17MNWqJ0,801
|
|
13
|
+
django_rubble/tests.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
django_rubble/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
django_rubble/utils/default_funcs.py,sha256=g-D0PtzyC40iKYfuKhoLYPyujaGNOHDyLTySHNHTVvU,206
|
|
16
|
+
django_rubble/utils/numbers.py,sha256=rk3MPd7nO34cc_5-Vy_zw760Z64wQauCATvcTGS11zk,3057
|
|
17
|
+
django_rubble/utils/strings.py,sha256=qu8hwhBIuL-zigFr9GNRcsEX0w17NhQ1A9peaig0ado,270
|
|
18
|
+
django_rubble/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
django_rubble/views/create_views.py,sha256=Vsh-QR6-Y3DpPpFtAgpDDfPS5JvqVf1De4_mQkaOwvM,668
|
|
20
|
+
django_rubble-0.2.0.dist-info/LICENSE,sha256=pD8j7afuQxx-5Ldl9fOxjKzprfeOGnDDr6NF8emKbBo,1104
|
|
21
|
+
django_rubble-0.2.0.dist-info/METADATA,sha256=ywzQiA6uB8mKXewkj5fBjgbnS7rFSjkNX7DrwTjdP0U,1603
|
|
22
|
+
django_rubble-0.2.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
23
|
+
django_rubble-0.2.0.dist-info/RECORD,,
|