Unverified Kaydet (Commit) 1d21a00f authored tarafından Uğur Özyılmazel's avatar Uğur Özyılmazel

Fix: default managers for base models

üst 44503153
......@@ -251,7 +251,7 @@ ignore-on-opaque-inference=yes
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local,__proxy__,Style,BaseModelWithSoftDelete
ignored-classes=optparse.Values,thread._local,_thread._local,__proxy__,Style,BaseModelWithSoftDelete,BasicPost
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
......
[![Build Status](https://travis-ci.org/vigo/django2-project-template.svg?branch=master)](https://travis-ci.org/vigo/django2-project-template)
![Python](https://img.shields.io/badge/django-3.7.0-green.svg)
![Django](https://img.shields.io/badge/django-2.1.3-green.svg)
![Version](https://img.shields.io/badge/version-1.0.1-yellow.svg)
![Version](https://img.shields.io/badge/version-1.1.0-yellow.svg)
# Django Project Starter Template
......@@ -760,7 +760,9 @@ This is a common model. By default, `BaseModel` contains these fields:
- `updated_at`
- `status`
Also has custom manager called: `objects_bm`. There are 4 basic status types:
We are overriding the default manager. `BaseModel` uses `BaseModelQuerySet` as
manager, `BaseModelWithSoftDelete` uses `BaseModelWithSoftDeleteManager`.
There are 4 basic status types:
```python
STATUS_OFFLINE = 0
......@@ -769,13 +771,13 @@ STATUS_DELETED = 2
STATUS_DRAFT = 3
```
Custom manager has custom querysets against these statuses such as:
You can make these queries:
```python
>>> Post.objects_bm.deleted() # filters: status = STATUS_DELETED
>>> Post.objects_bm.actives() # filters: status = STATUS_ONLINE
>>> Post.objects_bm.offlines() # filters: status = STATUS_OFFLINE
>>> Post.objects_bm.drafts() # filters: status = STATUS_DRAFT
>>> Post.objects.deleted() # filters: status = STATUS_DELETED
>>> Post.objects.actives() # filters: status = STATUS_ONLINE
>>> Post.objects.offlines() # filters: status = STATUS_OFFLINE
>>> Post.objects.drafts() # filters: status = STATUS_DRAFT
```
## `BaseModelWithSoftDelete`
......@@ -794,7 +796,7 @@ This works exactly like Django’s `delete()`. Broadcasts `pre_delete` and
a dictionary with the number of deletion-marks per object type.
```python
>>> Post.objects_bm.all()
>>> Post.objects.all()
SELECT "blog_post"."id",
"blog_post"."created_at",
......@@ -812,7 +814,7 @@ Execution time: 0.000135s [Database: default]
<BaseModelWithSoftDeleteQuerySet [<Post: Python post 1>, <Post: Python post 2>, <Post: Python post 3>]>
>>> Category.objects_bm.all()
>>> Category.objects.all()
SELECT "blog_category"."id",
"blog_category"."created_at",
......@@ -826,14 +828,14 @@ SELECT "blog_category"."id",
<BaseModelWithSoftDeleteQuerySet [<Category: Python>]>
>>> Category.objects_bm.delete()
>>> Category.objects.delete()
(4, {'blog.Category': 1, 'blog.Post': 3})
>>> Category.objects_bm.all()
>>> Category.objects.all()
<BaseModelWithSoftDeleteQuerySet []> # rows are still there! don’t panic!
>>> Category.objects.all()
<QuerySet [<Category: Python>]>
>>> Category.objects.deleted()
<BaseModelWithSoftDeleteQuerySet [<Category: Python>]>
```
......@@ -1406,46 +1408,7 @@ $ rake test:coverage
```bash
$ DJANGO_ENV=test python manage.py test baseapp -v 2 # or
$ DJANGO_ENV=test python manage.py test baseapp.tests.test_user.CustomUserTestCase # run single unit
$ rake test:baseapp
```
---
## Notes
If you created models via management command or rake task, you’ll have admin
file automatically and generated against your model type. If you created a model
with `BaseModelWithSoftDelete`, you’ll have `BaseAdminWithSoftDelete` set.
`BaseAdminWithSoftDelete` uses `objects_bm` in `get_queryset` and by default,
you’ll have extra actions and soft delete feature. If you don’t want to use
`objects_bm` manager, you need to override it manually:
```python
# example: blog/admin/post.py
from django.contrib import admin
from baseapp.admin import BaseAdminWithSoftDelete
from ..models import Post
__all__ = [
'PostAdmin',
]
class PostAdmin(BaseAdminWithSoftDelete):
# sticky_list_filter = None
# hide_deleted_at = False
def get_queryset(self, request):
return self.model.objects.get_queryset() # this line!
admin.site.register(Post, PostAdmin)
$ rake test:run[baseapp]
```
---
......@@ -1504,6 +1467,11 @@ This project is licensed under MIT
## Change Log
**2018-12-04**
- Update: `BaseModel` and `BaseModelWithSoftDelete` now override `objects` via
their own managers.
**2018-11-30**
- Update: flake8 ignores configured
......
......@@ -42,7 +42,7 @@ class BaseAdminWithSoftDelete(BaseAdmin):
hide_deleted_at = True
def get_queryset(self, request):
qs = self.model.objects_bm.get_queryset()
qs = self.model.objects.get_queryset()
if request.GET.get('status__exact', None):
if (
numerify(request.GET.get('status__exact'))
......
from django import forms
from django.contrib import admin
from django.contrib.auth.admin import (
UserAdmin as BaseUserAdmin,
)
from django.contrib.auth.forms import (
ReadOnlyPasswordHashField,
)
from django.db import models
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _
from ..forms import UserChangeForm, UserCreationForm
from ..models import User
from ..widgets import AdminImageFileWidget
__all__ = ['UserAdmin']
class UserChangeForm(forms.ModelForm):
password = ReadOnlyPasswordHashField(
label=_('Password'),
help_text=_(
'Raw passwords are not stored, so there is no way to see this '
'user\'s password, but you can change the password using '
'<a href="../password/">this form</a>.'
),
)
class Meta:
model = User
fields = (
'email',
'first_name',
'last_name',
'password',
'is_active',
'is_staff',
'is_superuser',
)
labels = {
'first_name': _('first name').title(),
'last_name': _('last name').title(),
}
def clean_password(self):
return self.initial['password']
class UserCreationForm(forms.ModelForm):
password1 = forms.CharField(
label=_('Password'), widget=forms.PasswordInput
)
password2 = forms.CharField(
label=_('Password confirmation'),
widget=forms.PasswordInput,
)
class Meta:
"""
`fields` property holds only required fields.
"""
model = User
fields = ('first_name', 'last_name')
labels = {
'first_name': _('first name').title(),
'last_name': _('last name').title(),
}
def clean_password2(self):
password1 = self.cleaned_data.get('password1')
password2 = self.cleaned_data.get('password2')
if (
password1
and password2
and password1 != password2
):
raise forms.ValidationError(
_('Passwords don\'t match')
)
return password2
def save(self, commit=True):
user = super(UserCreationForm, self).save(
commit=False
)
user.set_password(self.cleaned_data['password1'])
if commit:
user.save()
return user
class UserAdmin(BaseUserAdmin):
form = UserChangeForm
......
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import (
ReadOnlyPasswordHashField,
)
from django.utils.translation import ugettext_lazy as _
__all__ = ['UserChangeForm', 'UserCreationForm']
User = get_user_model()
class UserChangeForm(forms.ModelForm):
password = ReadOnlyPasswordHashField(
label=_('Password'),
help_text=_(
'Raw passwords are not stored, so there is no way to see this '
'user\'s password, but you can change the password using '
'<a href="../password/">this form</a>.'
),
)
class Meta:
model = User
fields = (
'email',
'first_name',
'last_name',
'password',
'is_active',
'is_staff',
'is_superuser',
)
labels = {
'first_name': _('first name').title(),
'last_name': _('last name').title(),
}
def clean_password(self):
return self.initial['password']
class UserCreationForm(forms.ModelForm):
password1 = forms.CharField(
label=_('Password'), widget=forms.PasswordInput
)
password2 = forms.CharField(
label=_('Password confirmation'),
widget=forms.PasswordInput,
)
class Meta:
"""
`fields` property holds only required fields.
"""
model = User
fields = ('first_name', 'last_name')
labels = {
'first_name': _('first name').title(),
'last_name': _('last name').title(),
}
def clean_password2(self):
password1 = self.cleaned_data.get('password1')
password2 = self.cleaned_data.get('password2')
if (
password1
and password2
and password1 != password2
):
raise forms.ValidationError(
_('Passwords don\'t match')
)
return password2
def save(self, commit=True):
user = super(UserCreationForm, self).save(
commit=False
)
user.set_password(self.cleaned_data['password1'])
if commit:
user.save()
return user
......@@ -160,8 +160,7 @@ class BaseModel(models.Model):
verbose_name=_('Status'),
)
objects = models.Manager()
objects_bm = BaseModelQuerySet.as_manager()
objects = BaseModelQuerySet.as_manager()
class Meta:
abstract = True
......@@ -173,8 +172,7 @@ class BaseModelWithSoftDelete(BaseModel):
null=True, blank=True, verbose_name=_('Deleted At')
)
objects = models.Manager()
objects_bm = BaseModelWithSoftDeleteManager()
objects = BaseModelWithSoftDeleteManager()
class Meta:
abstract = True
......
......@@ -30,7 +30,7 @@ class BaseModelTestCase(TestCase):
def test_basemodel_queryset(self):
self.assertQuerysetEqual(
BasicPost.objects_bm.all().order_by('id'),
BasicPost.objects.all().order_by('id'),
[
'<BasicPost: Test Post 1>',
'<BasicPost: Test Post 2>',
......@@ -39,18 +39,18 @@ class BaseModelTestCase(TestCase):
],
)
self.assertQuerysetEqual(
BasicPost.objects_bm.actives().order_by('id'),
BasicPost.objects.actives().order_by('id'),
['<BasicPost: Test Post 1>'],
)
self.assertQuerysetEqual(
BasicPost.objects_bm.offlines(),
BasicPost.objects.offlines(),
['<BasicPost: Test Post 3>'],
)
self.assertQuerysetEqual(
BasicPost.objects_bm.deleted(),
BasicPost.objects.deleted(),
['<BasicPost: Test Post 2>'],
)
self.assertQuerysetEqual(
BasicPost.objects_bm.drafts(),
BasicPost.objects.drafts(),
['<BasicPost: Test Post 4>'],
)
......@@ -36,17 +36,17 @@ class BaseModelWithSoftDeleteTestCase(TestCase):
],
)
self.assertQuerysetEqual(
Category.objects_bm.actives(),
Category.objects.actives(),
['<Category: Python>'],
)
self.assertQuerysetEqual(
Category.objects_bm.offlines(), []
Category.objects.offlines(), []
)
self.assertQuerysetEqual(
Category.objects_bm.deleted(), []
Category.objects.deleted(), []
)
self.assertQuerysetEqual(
Category.objects_bm.drafts(), []
Category.objects.drafts(), []
)
def test_softdelete(self):
......@@ -56,14 +56,11 @@ class BaseModelWithSoftDeleteTestCase(TestCase):
(3, {'baseapp.Category': 1, 'baseapp.Post': 2}),
)
self.assertQuerysetEqual(
Category.objects_bm.deleted(),
Category.objects.deleted(),
['<Category: Python>'],
)
self.assertQuerysetEqual(
Category.objects.all(), ['<Category: Python>']
)
self.assertQuerysetEqual(
Post.objects_bm.deleted().order_by('id'),
Post.objects.deleted().order_by('id'),
[
'<Post: Python post 1>',
'<Post: Python post 2>',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment