I'm trying to create more separation of concerns with endpoints in my Django apps but not sure how to implement a service layer. I've read through a popular python article on this topic however it doesn't seem to answer the testing issue.
For instance, if I have a request come in to save a user and want to send an email after the user has been saved, it's popular to handle this logic by overriding on save like this:
**models.py**
class User(AbstractBaseUser, PermissionsMixin):
...
def save(self, *args, **kwargs):
if self._state.adding:
user.activate_user(user=self)
super(User, self).save(*args, **kwargs)
**services.py**
from services import iNotificationService
...
def activate_user(user):
user.active = True
iNotificationService().send_message(user)
In the above example, iNotificationService would be an interface that the application could choose at runtime. If this user was saved in a production environment, the application would provide a class like the following
import mandrill
class EmailClient(iNotificationService)
def send_message(user):
message = create_message(user)
mandrill.send_email(message)
into the services.py module so that an email was sent. But if the application was run in a testing environment, the test would mock the EmailClient by sending in an instance of the interface so that no email would actually be sent:
class iNotificationService(object)
def send_message(user)
pass
What I'm wondering is how I supply the instance to the services.py module so that activate_user knows which type of notification to send. My only idea around this was to pass some arg to the save method so that it would know which type of notification to use. But I'm wondering how scalable that solution would be considering the different places one might use a service.
If change in notification provider class only depends on environment (testing / development / production etc) you are running application in (as you have mentioned), I would suggest you to provide notification class to your application through settings. I personally use this pattern and have seen in many projects and find it very useful.
Say you have a services package which has notifications module in it containing all notification provider classes. Like this.
services
__init__.py
notifications.py
iNotificationService
EmailClient
Add notification provider class path in settings.py file.
settings.py
...
# this value can be changed / overridden based on environment.
# pragmatically or statically.
NOTIFICATION_PROVIDER = 'services.notifications.iNotificationService'
...
services/__init__.py
from django.conf import settings
from django.utils.module_loading import import_string
def get_notification_provider(*args, **kwargs):
try:
notification_class = import_string(settings.NOTIFICATION_PROVIDER)
except ImportError as e:
raise ImproperlyConfigured('Error loading notification provider: %s' % e)
return notification_class(*args, **kwargs)
Now you can import get_notification_provider from services package and use it across your application to get an instance of notification class based on your settings. You can plug in different providers in settings based on your needs / environment.
Related
I have a OneToOneField between my UserTrust model and django.contrib.auth.models.User that I would like to create whenever a new User registers. I thought on creating the UserTrust instance using the user_signed_up signal.
I have the following code in my AppConfig
def ready(self):
# importing model classes
from .models import UserTrust
#receiver(user_signed_up)
def create_usertrust(sender, **kwargs):
u = kwargs['user']
UserTrust.objects.create(user=u)
... and this is in my pytest test
#pytest.fixture
def test_password():
return 'strong-test-pass'
#pytest.fixture
def create_user(db, django_user_model, test_password):
def make_user(**kwargs):
kwargs['password'] = test_password
if 'username' not in kwargs:
kwargs['username'] = str(uuid.uuid4())
return django_user_model.objects.create_user(**kwargs)
return make_user
#pytest.mark.django_db
def test_my_user(create_user):
user = create_user()
assert user.usertrust.available == 1000
Still, the test fails with
django.contrib.auth.models.User.usertrust.RelatedObjectDoesNotExist: User has no usertrust.
What did I miss?
The problem with you creating the user via the django_user_model is that it doesn't actually pass through the allauth code that actually sends that signal. You've got two options:
Use a client (since I'm assuming you're using pytest-django) and fill out a registration via the allauth provided link for registering new users. That way, a signal is sent, and you can assert attributes and model instances like that.
You can simply ignore the signal and unittest the function itself. That's it. You put your trust in that single registration view not changing at all and put your trust in the package that the API will not change. I can still recommend this option, but you've been warned.
You can send the signal yourself. Not recommended in case allauth's API changes, but you could just import the signal from allauth and send it like this: user_signed_up.send(sender=self.__class__, toppings=toppings, size=size) where user_signed_up is the signal. Ref the docs: https://docs.djangoproject.com/en/dev/topics/signals/#sending-signals
Again, definitely recommend the first one in case of API changes. I can also recommend the second option just because allauth is pretty reputable and you know what going to happen without too huge of an package change, but you never know.
I have a model I am sending email and sms to user in post_save signal I am creating the model multiple times so it is sending email and sms multiple time.
I am planning to write new test for testing sms and email.
def send_activation_mail_sms(sender, instance, created, **kwargs):
if created :
mobile_activation = UserMobileActivation.objects.create(user=instance,randomword=randomword(50),ref=ref)
email_activation = UserEmailActivation.objects.create(user=instance,randomword=randomword(50),ref=ref)
url_email = "{0}view/v1/email/activation/{1}/".format(HOSTNAME,email_activation.randomword) short_url_email = url_shortener(long_url_email)
url_sms = "{0}view/v1/mobile/activation/{1}".format(HOSTNAME,mobile_activation.randomword)
app.send_task("apps.tasks.send_sms",
args=[TEXTLOCAL_APIKEY,mobile_activation.stockuser.user.username ,'TXTLCL','Activate your mobile here {0}'.format(url_sms)])
app.send_task("apps.tasks.send_email",
args=[email_activation.user.user.email, EMAIL_VERIFICATION_SUBJECT,
EMAIL_VERIFICATION_TEMPLATE, {"host": HOSTNAME, "verify_email_url": url_email}])
I am passing created arg in post_save signal is there any way I can pass extra arg here so that while doing python manage.py test it will skip sending sms and email. I used versioning one way I was thinking to have different version of API for testing but as there is no request coming here I cannot catch request.version here. Please suggest.
Initially set some variable in your settings.py to identify the environment currently working on
# settings.py
MY_ENV = "DEVELOPMENT"
Then, run the celery tasks/additional scripts based on the MY_ENV
from django.conf import settings
def send_activation_mail_sms(sender, instance, created, **kwargs):
if created and settings.MY_ENV == "DEVELOPMENT":
# do your stuff
Django provide us to override the settings configs during the testing, see the doc Override Settings. So you could override the MY_ENV value in the test itself
I have a Mezzanine blog and I would like to add a little form to every page so users can type their email addresses and click 'subscribe' so, from that moment, an email will be sent to announce any new post to the blog.
I don't see that built in or any existing module for that purpose... Should I program that from scratch? Any ideas?
Since there are no answers, I will try to offer my best guess as to the easiest strategy in this situation. I don't know of any built-in functions in Mezzanine that perform this specific function, so here's how I would approach it:
python manage.py startapp subscriber
Build out basic model - email = models.EmailField(unique=True), etc.
Create basic admin.py
Update settings.py -> INSTALLED_APPS and ADMIN_MENU_ORDER
ADMIN_MENU_ORDER = (
(_("Web Content"), ((_("Pages"), "pages.Page"),
(_("Subscribers"), "subscriber.Subscriber"),
Create a forms.py - something like this:
class SubscriberForm(forms.ModelForm):
class Meta:
model = Subscriber
fields = ['email']
Setup a views.py for GET/POST of above form
Reconfigure urls.py to redirect to the new view function
Then, perhaps the only interesting part of my response, I would copy the Mezzanine Blog app directory into the project directory, and create a signals.py. Maybe add something like this:
#receiver(pre_save, sender=BlogPost, dispatch_uid="save_blogpost")
def save_blogpost(sender, instance, **kwargs):
""" Every time a new blog post is created, notify all subscribers."""
if instance.published:
subs = Subscriber.objects.all()
for sub in subs:
email = EmailMultiAlternatives(
subject="New Blog Post!",
body="A new blog post has been added!",
from_email="example#email.com",
to=[sub.email]
)
email.attach_alternative(self.body, "text/html")
email.send()
Update app.py in the blog app directory to contain this under the Config class:
def ready(self):
import blog.signals # noqa
If you've got Django configured to send mail through an external SMTP mail server, that's easier. If not, you'll likely want to setup Postfix with OpenDKIM and configure SPF to reduce probability the outgoing mail ends up in spam.
(Also, you'll obviously need to handle situations where an admin changes a draft post to a published post, etc.)
Not sure if this helps, but hopefully!
I used Flask-Restful in a project where i also use the Factory pattern to create Flask objects. The problem now is that Flask give me 404 error when i try to reach http://localhost:5000/api/v1/user/ but when i explore (via the debugger) the Flask app object's url_map, my API rule is there. So, if someone ever had the same issue, i'm taking whatever possible solution.
I have the following function creating the API app:
def create_app(settings_override=None):
"""
Returns the API :class:`Flask` instance.
:param settings_override: dictionary of settings to override.
"""
app = factory.create_app(__name__, __path__, settings_override)
api = Api(app, prefix='/api/v1', catch_all_404s=True)
# API endpoints connected to the User model.
api.add_resource(UserAPI, '/user/', endpoint='user')
return app
The code of UserAPI class (used by Flask-Restful):
class UserAPI(Resource):
"""
API :class:`Resource` for returning the details of a user.
This endpoint can be used to verify a user login credentials.
"""
def get(self):
return {'hello': 'world'}, 200
def post(self):
pass
The factory.create_app function:
def create_app(package_name, package_path, settings_override=None):
"""
Returns an instance of Flask configured with common functionnalities for
Cubbyhole.
:param package_name: application package name
:param package_path: application package path
:param settings_override: a dictionnary of settings to override
"""
app = Flask(package_name, instance_relative_config=True)
app.config.from_object('cubbyhole.settings')
app.config.from_pyfile('settings.cfg', silent=True)
if settings_override is not None:
app.config.update(settings_override)
db.init_app(app)
register_blueprints(app, package_name, package_path)
return app
Python version 2.7
Flask v.
Flask-Restful version
After some investigations and some questions on Flask's IRC channel, i found that when using custom domain name, the port number should be set via the SERVER_NAME config variable. So, the issue was not coming from the factory code.
If you want to access the server via http://myserver.io:5000/, you set the port, here 5000, in SERVER_NAME as SERVER_NAME = myserver.io:5000.
This single modification in my settings worked for me :) Thanks !
I have some external services. My Django app is built on top of my external service APIs. In order to talk to my external service, I have to pass in an auth cookies, which I can get by reading User (that cookie != django cookies).
Using test tools like webtests, requests, I have trouble writing my tests.
class MyTestCase(WebTest):
def test_my_view(self):
#client = Client()
#response = client.get(reverse('create')).form
form = self.app.get(reverse('create'), user='dummy').form
print form.fields.values()
form['name'] = 'omghell0'
print form
response = form.submit()
I need to submit a form, which creates, say, a user on my external service. But to do that, I normally would pass in request.user (in order to authenticate my privilege to external service). But I don't have request.user.
What options do I have for this kind of stuff?
Thanks...
Suppose this is my tests.py
import unittest
from django.test.client import Client
from django.core.urlresolvers import reverse
from django_webtest import WebTest
from django.contrib.auth.models import User
class SimpleTest(unittest.TestCase):
def setUp(self):
self.usr = User.objects.get(username='dummy')
print self.usr
.......
I get
Traceback (most recent call last):
File "/var/lib/graphyte-webclient/webclient/apps/codebundles/tests.py", line 10, in setUp
self.usr = User.objects.get(username='dummy')
File "/var/lib/graphyte-webclient/graphyte-webenv/lib/python2.6/site-packages/django/db/models/manager.py", line 132, in get
return self.get_query_set().get(*args, **kwargs)
File "/var/lib/graphyte-webclient/graphyte-webenv/lib/python2.6/site-packages/django/db/models/query.py", line 341, in get
% self.model._meta.object_name)
DoesNotExist: User matching query does not exist
But if I test the User.objects in views, I am okay.
You need to use the setUp() method to create test users for testing - testing never uses live data, but creates a temporary test database to run your unit tests. Read this for more information: https://docs.djangoproject.com/en/dev/topics/testing/?from=olddocs#writing-unit-tests
EDIT:
Here's an example:
from django.utils import unittest
from django.contrib.auth.models import User
from myapp.models import ThisModel, ThatModel
class ModelTest(unittest.TestCase):
def setUp(self):
# Create some users
self.user_1 = User.objects.create_user('Chevy Chase', 'chevy#chase.com', 'chevyspassword')
self.user_2 = User.objects.create_user('Jim Carrey', 'jim#carrey.com', 'jimspassword')
self.user_3 = User.objects.create_user('Dennis Leary', 'dennis#leary.com', 'denisspassword')
Also note that, if you are going to use more than one method to test different functionality, you should use the tearDown method to destroy objects before reinstantiating them for the next test. This is something that took me a while to finally figure out, so I'll save you the trouble.
def tearDown(self):
# Clean up after each test
self.user_1.delete()
self.user_2.delete()
self.user_3.delete()
Django recommends using either unit tests or doc tests, as described here. You can put these tests into tests.py in each apps directory, and they will run when the command `python manage.py test" is used.
Django provides very helpful classes and functions for unit testing, as described here. In particular, the class django.test.Client is very convenient, and lets you control things like users.
https://docs.djangoproject.com/en/1.4/topics/testing/#module-django.test.client
Use the django test client to simulate requests. If you need to test the behavior of the returned result then use Selenium.