Skip to content

Commit

Permalink
refactor: Switch to mongoengine style field arguments. Drop old django (
Browse files Browse the repository at this point in the history
#200)

BREAKING CHANGE: Removed `blank` argument support for fields, use mongoengine `required` instead.
  • Loading branch information
last-partizan authored Nov 15, 2023
1 parent 1a08b5a commit 9d56f6a
Show file tree
Hide file tree
Showing 11 changed files with 138 additions and 118 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ jobs:
strategy:
matrix:
python:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "pypy3.9"
- "pypy3.10"
django:
- "Django>=3.2,<3.3"
- "Django>=4.2,<4.3"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
8 changes: 4 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Right now we're targeting to get things working on Django 4.2;
WARNING: This project is not in good state, and is likely to break with django updates.
It's better to use raw mongoengine.

Working / Django 3.2-4.2
Working / Django 4.2
------------------------

* [ok] sessions
Expand All @@ -50,9 +50,9 @@ It get's replaced after class creation via some metaclass magick.
Fields notes
------------

* mongo defaults Field(required=False), changed to django-style defaults
-> Field(blank=False), and setting required = not blank in Field.__init__

* Project uses mongoengine style argument `required=False`, not django style `blank=False`,
to be compatible with mongo-types.
**All your fields are optional by default.**


TODO
Expand Down
6 changes: 3 additions & 3 deletions django_mongoengine/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from __future__ import annotations

from functools import partial
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

from bson.objectid import ObjectId
from django.db.models import Model
Expand All @@ -23,7 +23,7 @@
# TopLevelDocumentMetaclass is using ObjectIdField to create default pk field,
# if one's not set explicitly.
# We need to know it's not editable and auto_created.
mtc.ObjectIdField = partial(ObjectIdField, editable=False, auto_created=True, blank=True)
mtc.ObjectIdField = partial(ObjectIdField, editable=False, auto_created=True)


def django_meta(meta, *top_bases):
Expand Down Expand Up @@ -51,7 +51,7 @@ class DjangoFlavor:
objects = QuerySetManager["Self"]()
_default_manager = QuerySetManager["Self"]()
_get_pk_val = Model.__dict__["_get_pk_val"]
_meta: DocumentMetaWrapper
_meta: DocumentMetaWrapper | dict[str, Any]
DoesNotExist: type[DoesNotExist]

def __init__(self, *args, **kwargs):
Expand Down
28 changes: 15 additions & 13 deletions django_mongoengine/fields/djangoflavor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@

from .internal import INTERNAL_DJANGO_FIELDS_MAP

_field_defaults = (
("blank", False),
("null", False),
("help_text", ""),
("editable", True),
("auto_created", False),
)
# Add some default values, required for django.
_field_defaults = {
"help_text": "",
"editable": True,
"auto_created": False,
}


class DjangoField:
Expand All @@ -37,13 +36,13 @@ def _get_flatchoices(self):
flatchoices = property(_get_flatchoices)

def __init__(self, *args, **kwargs):
for k, v in _field_defaults:
kwargs.setdefault(k, v)
if "required" in kwargs:
kwargs = _field_defaults | kwargs

if "blank" in kwargs:
raise ImproperlyConfigured(
"`required` option is not supported. Use Django-style `blank` instead."
"`blank` option is not supported. Use Mongoengine-style `required` instead."
)
kwargs["required"] = not kwargs["blank"]

if hasattr(self, "auto_created"):
kwargs.pop("auto_created")
self._verbose_name = kwargs.pop("verbose_name", None)
Expand All @@ -53,6 +52,9 @@ def __init__(self, *args, **kwargs):

self.remote_field = None
self.is_relation = self.remote_field is not None
# This is needed for django, but we use mongoengine style in __init__
# to not confuse type checker.
self.blank = not self.required

def _get_verbose_name(self):
return self._verbose_name or self.db_field.replace('_', ' ')
Expand Down Expand Up @@ -272,7 +274,7 @@ def formfield(self, **kwargs):
elif isinstance(self.field, fields.ReferenceField):
defaults = {
'form_class': formfields.DocumentMultipleChoiceField,
'queryset': self.field.document_type.objects,
'queryset': self.field.document_type.objects, # type: ignore
}
else:
defaults = {}
Expand Down
44 changes: 9 additions & 35 deletions django_mongoengine/mongo_auth/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
from django.contrib.contenttypes.models import ContentTypeManager
from django.db import models
from django.utils import timezone
from django.utils.encoding import smart_str
from django.utils.translation import gettext_lazy as _
from mongoengine import ImproperlyConfigured

from django_mongoengine import document, fields
from django_mongoengine.queryset import QuerySetManager

from django.contrib.auth.hashers import check_password, make_password
from .managers import MongoUserManager


Expand All @@ -34,32 +34,6 @@ def ct_init(self, *args, **kwargs):
),
)

try:
from django.contrib.auth.hashers import check_password, make_password
except ImportError:
"""Handle older versions of Django"""
from django.utils.hashcompat import md5_constructor, sha_constructor

def get_hexdigest(algorithm, salt, raw_password):
raw_password, salt = smart_str(raw_password), smart_str(salt)
if algorithm == 'md5':
return md5_constructor(salt + raw_password).hexdigest()
elif algorithm == 'sha1':
return sha_constructor(salt + raw_password).hexdigest()
raise ValueError('Got unknown password algorithm type in password')

def check_password(raw_password, password):
algo, salt, hash = password.split('$')
return hash == get_hexdigest(algo, salt, raw_password)

def make_password(raw_password):
from random import random

algo = 'sha1'
salt = get_hexdigest(algo, str(random()), str(random()))[:5]
hash = get_hexdigest(algo, salt, raw_password)
return '%s$%s$%s' % (algo, salt, hash)


class BaseUser:
is_anonymous = AbstractBaseUser.__dict__['is_anonymous']
Expand Down Expand Up @@ -88,7 +62,7 @@ class Meta:
# ordering = ('name',)
# unique_together = (('app_label', 'model'),)

def __unicode__(self):
def __str__(self):
return self.name

def model_class(self):
Expand Down Expand Up @@ -158,7 +132,7 @@ class Meta:
# unique_together = (('content_type', 'codename'),)
# ordering = ('content_type__app_label', 'content_type__model', 'codename')

def __unicode__(self):
def __str__(self):
return "%s | %s | %s" % (
self.content_type.app_label,
self.content_type,
Expand Down Expand Up @@ -195,7 +169,7 @@ class Meta:
verbose_name = _('group')
verbose_name_plural = _('groups')

def __unicode__(self):
def __str__(self):
return self.name


Expand All @@ -208,22 +182,23 @@ class AbstractUser(BaseUser, document.Document):
max_length=150,
verbose_name=_('username'),
help_text=_("Required. 150 characters or fewer. Letters, numbers and @/./+/-/_ characters"),
required=True,
)

first_name = fields.StringField(
max_length=30,
blank=True,
verbose_name=_('first name'),
)

last_name = fields.StringField(max_length=30, blank=True, verbose_name=_('last name'))
email = fields.EmailField(verbose_name=_('e-mail address'), blank=True)
last_name = fields.StringField(max_length=30, verbose_name=_('last name'))
email = fields.EmailField(verbose_name=_('e-mail address'), required=True)
password = fields.StringField(
max_length=128,
verbose_name=_('password'),
help_text=_(
"Use '[algo]$[iterations]$[salt]$[hexdigest]' or use the <a href=\"password/\">change password form</a>."
),
required=True,
)
is_staff = fields.BooleanField(
default=False,
Expand All @@ -250,7 +225,6 @@ class AbstractUser(BaseUser, document.Document):
user_permissions = fields.ListField(
fields.ReferenceField(Permission),
verbose_name=_('user permissions'),
blank=True,
help_text=_('Permissions for the user.'),
)

Expand All @@ -259,7 +233,7 @@ class AbstractUser(BaseUser, document.Document):

meta = {'abstract': True, 'indexes': [{'fields': ['username'], 'unique': True, 'sparse': True}]}

def __unicode__(self):
def __str__(self):
return self.username

def get_full_name(self):
Expand Down
46 changes: 23 additions & 23 deletions example/tumblelog/tumblelog/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
try:
from django.urls import reverse
except ImportError:
from django.core.urlresolvers import reverse
from django.urls import reverse

import datetime

Expand All @@ -13,58 +10,61 @@ class Comment(EmbeddedDocument):
default=datetime.datetime.now,
editable=False,
)
author = fields.StringField(verbose_name="Name", max_length=255)
email = fields.EmailField(verbose_name="Email", blank=True)
body = fields.StringField(verbose_name="Comment")
author = fields.StringField(verbose_name="Name", max_length=255, required=True)
email = fields.EmailField(verbose_name="Email", required=True)
body = fields.StringField(verbose_name="Comment", required=True)


class Post(Document):
created_at = fields.DateTimeField(
default=datetime.datetime.now,
editable=False,
)
title = fields.StringField(max_length=255)
slug = fields.StringField(max_length=255, primary_key=True)
title = fields.StringField(max_length=255, required=True)
slug = fields.StringField(max_length=255, primary_key=True, required=True)
comments = fields.ListField(
fields.EmbeddedDocumentField('Comment'),
fields.EmbeddedDocumentField(Comment, required=True),
default=[],
)
strings = fields.ListField(
fields.StringField(required=True),
default=[],
blank=True,
)

def get_absolute_url(self):
return reverse('post', kwargs={"slug": self.slug})
return reverse("post", kwargs={"slug": self.slug})

def __unicode__(self):
def __str__(self):
return self.title

@property
def post_type(self):
return self.__class__.__name__

meta = {
'indexes': ['-created_at', 'slug'],
'ordering': ['-created_at'],
'allow_inheritance': True,
"indexes": ["-created_at", "slug"],
"ordering": ["-created_at"],
"allow_inheritance": True,
}


class BlogPost(Post):
body = fields.StringField()
body = fields.StringField(required=True)


class Video(Post):
embed_code = fields.StringField()
embed_code = fields.StringField(required=True)


class Image(Post):
image = fields.ImageField()
image = fields.ImageField(required=True)


class Quote(Post):
body = fields.StringField()
author = fields.StringField(verbose_name="Author Name", max_length=255)
body = fields.StringField(required=True)
author = fields.StringField(verbose_name="Author Name", max_length=255, required=True)


class Music(Post):
url = fields.StringField(max_length=100, verbose_name="Music Url")
music_parameters = fields.DictField(verbose_name="Music Parameters")
url = fields.StringField(max_length=100, verbose_name="Music Url", required=True)
music_parameters = fields.DictField(verbose_name="Music Parameters", required=True)
Loading

0 comments on commit 9d56f6a

Please sign in to comment.