Django Testing - loading data into the database before loading the apps - python

I'm currently writting some tests for a Django app (+ REST framework), and having some issues loading the test data into the database.
Let me explain with some (very simplified) code :
I have a django view which is something like :
view.py
from myapp.models import Item
from myapp.utils import MyClass
# need to initialize with the set of items
item_set = {item.name for item in Item.objects.all()}
my_class_object = MyClass(item_set)
class MyView(APIView):
def post(selfself, request):
result = my_class_object.process(request.data)
return Response(result)
So basically I need to initialize a class with some data from the database, and I then use this class in my view to process the data received by the endpoint.
Now the test :
my_test.py
from rest_framework.test import APILiveServerTestCase
from myapp.models import Item
class MyTest(APILiveServerTestCase):
def setUp(self):
self.URL = '/some_url_linking_to_myview/'
# load some data
Item.objects.create(name="first item")
Item.objects.create(name="second item")
def test_myview_return_correct_result(self):
post_data = {"foo"}
response = self.client.post(self.URL,
data=post_data,
format='json')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data, {"my_expected_result"})
When running the test, what currently happens is that view.py is loaded before the setUp() method get excecuted, so when I instantiate the class with these two lines :
item_set = {item.name for item in Item.objects.all()}
my_class_object = MyClass(item_set)
the database is still empty.
I'm wondering if there is a way to either get the data into the database before view.py get executed, or maybe somehow force reloading the app after setUp(), or instantiate my class somewhere else so it gets called after loading the data ?
thanks !

I think you are looking for setUpTestData().
Here is roughly how I set this up if I am going to use a significant amount of data:
tests.py
from django.test import TestCase
from .tests.test_data import base_data
class MyClassTest(TestCase):
#classmethod
def setUpTestData(cls):
base_data.base_data(cls)
base_data.py
from .models import MyClass
def base_data(cls):
cls.MyClass1 = MyClass.objects.create(
name="first_name"
)
cls.MyClass2 = MyClass.objects.create(
name="second_name"
)
Of course, you can do everything directly in the setUpTestData() function, if you would rather have your test data sitting up top.

Why not put the initiate code in the function and call from inside the post.
class MyView(APIView):
def initialize(self):
item_set = {item.name for item in Item.objects.all()}
my_class_object = MyClass(item_set)
def post(self, request):
self.initialize()
result = my_class_object.process(request.data)
return Response(result)
Edit 1
Optionally, you can use fixture to load MyClass objects in the database beforehand
class MyTest(APILiveServerTestCase):
fixtures = [
// my class objects fixtures file
]
def setUp():
// rest of the code

Related

flask how to preserve instance of SQLAlchemyobject

I want to load a global menu for any template. The structure of the Query is always the same...
class ProductCats(db.Model):
def __init__(self,
lang=None,
catname=None,
catid=None,
update=False,
active=False,
create=False,
):
self.categories = db.session.query(ProductCats, ProductCatsTranslations)
if lang:
self.categories = self.categories.filter(ProductCatsTranslations.language_code==lang)
if catid:
self.categories = self.categories.filter(ProductCatsTranslations.catid==catid)
if active:
self.categories = self.categories.filter(ProductCats.active==active)
self.categories = self.categories.filter(ProductCatsTranslations.catid==ProductCats.catid).\
order_by(ProductCatsTranslations.sort)
self.dicted_by_catid = {}
for c, ct in self.categories:
c.ct = ct
self.dicted_by_catid[c.catid] = c
# then I have a method within this class
def by_catid(self, catid=None):
return self.dicted_by_catid[catid]
in init.py of my flask app
#app.before_request
def before_request():
from models import ProductCats
g.productcats = ProductCats(lang=lang)
So I can easily access my productcats everywhere on flaskapp. But it initiates one Query everytime instead of getting it from the dictonary "by_catid". I call something like this. e.g. in the template or on views or where ever in this app.
g.productcats.by_catid(catid=12).name
g.productcats.by_catid(catid=172).name
This produces two Queries instead of one from init of ProductCats
When I manually set up the same db.query of init of ProductCats() somewhere else in my application e.g. on my homeview I can call the attribute any time without it creates new Query everytime.
There seems to be something special, that g.object is calling the init method of ProductCats everytime again? But when I add a simple debugoutput in my ProductCats init it appears only ONE time. Also the relationship to ProductCatsTranslation is lazy-joined. Also it works in my homeview when not using flask-global (g).
How would I have my productcats all over the app without generating one Query for every category while I can still use the instance of my Productcats in my templates e.g.
g.productcats.by_catid(catid=12).name
g.productcats.by_catid(catid=14).otherattribute
g.productcats.by_catid(catid=12).active

Django Test Object created is empty

I am trying to follow instructions from the Django documentation:
https://docs.djangoproject.com/en/3.2/topics/testing/overview/
However when I try to create the object for the test. The object is empty??
This is my code:
from django.test import TestCase, Client
from django.urls import reverse
from .models import ShortURL
from .views import generate_shortURL, redirect, home
from datetime import datetime
url = "https://stackoverflow.com/"
time = datetime.now()
class TestURLShortener(TestCase):
def setup(self):
self.client = Client()
obj= ShortURL.objects.create(original_url=url, short_url='zqSkSQ', time_date_created=time, count=2)
obj.save()
def test_creating_short_URL_POST(self):
"""
Test to create short Urls
"""
short_url_from_db = ShortURL.objects.all()
print(f'short_url_from_db : {short_url_from_db}')
response = self.client.post(reverse('generate_shortURL'), data={'original_url': url})
generated_short_url = response.context["chars"]
self.assertEquals(generated_short_url, 'afasdf')
This is the results when I run the test:
short_url_from_db prints out this <QuerySet []> instead of the object I wanted it to print out from the setup function.
How can I get the object I created to use in this test?
You need to use setUp and not setup as the function for setting up your test case.
You also don't need to call save() if you use create().
An alternative you could make use of is setUpTestData()
This technique allows for faster tests as compared to using setUp().
class TestURLShortener(TestCase):
#classmethod
def setUpTestData(cls):
# Set up data for the whole TestCase
cls.obj = ShortURL.objects.create(original_url=url, short_url='zqSkSQ', time_date_created=time, count=2)
...
def setUp(self):
self.client = Client()
def test_obj_type(self):
self.assertTrue(isinstance(self.obj, ShortURL))

Django | Adding/changing code in 3rd party packages with monkey patching

(+/- 3 months experience with Django)
I'm using the django-newsletter package to create and send newsletters to subscribers on my website. It uses a message.html template for which the context is added in the Submission model:
class Submission(models.Model):
# Some irrelevant code, only showing the send_message method below:
def send_message(self, subscription):
variable_dict = {
'subscription': subscription,
'site': Site.objects.get_current(),
'submission': self,
'message': self.message,
'newsletter': self.newsletter,
'date': self.publish_date,
'STATIC_URL': settings.STATIC_URL,
'MEDIA_URL': settings.MEDIA_URL
}
unescaped_context = get_context(variable_dict, autoescape=False)
subject = self.message.subject_template.render(
unescaped_context).strip()
text = self.message.text_template.render(unescaped_context)
message = EmailMultiAlternatives(
subject, text,
from_email=self.newsletter.get_sender(),
to=[subscription.get_recipient()],
headers=self.extra_headers,
)
if self.message.html_template:
escaped_context = get_context(variable_dict)
message.attach_alternative(
self.message.html_template.render(escaped_context),
"text/html"
)
# some more code irrelevant to the question
message.send()
Essentially upon Submitting the newsletter, the context is added in variable_dict in the Submission.send_message method.
In order to add more Personalization possibilities I would like to add my own context variables to this. From what I understand is that it's best to use monkey patching to achieve this, but I am not able to access the variable_dict from outside the method itself. Below is my monkey patch code:
## Monkey Patching ##
# monkey_patches.py
DJANGO_NEWSLETTER_CONTEXT_PATCH = True # adds context to Submission
def monkey_patch():
if DJANGO_NEWSLETTER_CONTEXT_PATCH:
from newsletter.models import Submission
old_send_message = Submission.send_message
def new_send_message(self, *k, **kw):
old_send_message(self, *k, **kw)
# trying to append to variable dict here, but failing:
print(variable_dict) # gives error
# self.send_message.variable_dict['test'] = 'testvalue' # gives error
# variable_dict['test'] = 'testvalue' # gives error
Submission.send_message = new_send_message
# apps.py
from django.apps import AppConfig
class SomeAppConfig(AppConfig):
name = 'SomeApp'
def ready(self):
try:
from SomeApp.monkey_patches import monkey_patch
monkey_patch()
except ImportError:
pass
Would really appreciate some help. Any pointers are welcome.

inject library dependencies into django model

I have a Django model that makes use of some libraries which I would like to be able to override. For instance, when testing I'd like to pass a mock instead of having my model tightly coupled. I can do this in python, but for the life of me I can't figure out how to do it with a Django model. Here's a simplified example not using Django:
import requests
class APIClient:
def __init__(self, **kwargs):
self.http_lib = kwargs.get("http_lib", requests)
def get_url(self, url):
return self.http_lib.get(url)
For regular use of this class I can still use requests but if I want to use a different library for some reason or if I want to test certain outcomes, I can invoke the class with client = APIClient(http_lib=MockRequests())
But how do I do that with a Django model? If I try to pass kwargs that aren't backed by a database field Django throws an error. Overriding __init__ is not considered a good practice either. Is there a way in Django to set and get a value that isn't backed by a database column?
Do you have a settings.TEST var? If so, you could make http_lib a function that returns the proper lib:
from django.conf import settings
def get_http_lib(mock=None):
if not mock:
return requests
return MockRequests()
class APIClient(Model):
def __init__(self, **kwargs):
# ...whatever...
#property
def some_column(self):
http_lib = get_http_lib(settings.TEST)
# ...etc...
Not ideal, but passable.
PRE-EDIT ANSWER (doesn't work):
What if you setattr subsequent to instantiating the Model?
# In model...
class APIClient(Model):
def __init__(self, **kwargs):
self.http_lib = requests
# ...etc...
# In tests...
client = APIClient()
setattr(client, 'http_lib', MockRequests())

How to make Class instance in main.py available in another file?

I am having difficulty where I need to make the information available in a class that is instantiated in main.py in another file. The best way to describe what I am trying to do can be seen in the flow diagram below:
The issue as you can imagine is with circular dependency. Is there a way to create an interface between schema.py and main.py so that I can pass class information?
Thank you for your time and any help you can offer!
EDIT: Added code for reference
ws_transport.py
from autobahn.twisted.websocket import (
WebSocketServerProtocol,
WebSocketServerFactory,
)
from schema import schema
class WsProtocol(WebSocketServerProtocol):
def __init__(self):
# Code here
def onConnect(self, request):
# Code here
def onMessage(self, payload, isBinary):
# Code here
class WsProtocolFactory(WebSocketServerFactory):
def __init__(self):
super(WsProtocolFactory, self).__init__()
self.connection_subscriptions = defaultdict(set)
# Code here
def check_events_db(self):
# Code here
def check_audit_log_db(self):
# Code here
web_transport.py
import sys, os
import json
from twisted.web.resource import Resource
from twisted.web.server import Site, http
from schema import schema
class HttpResource(Resource):
isLeaf = True
def render_OPTIONS(self, request):
# Code here
def render_GET(self, request):
# Code here
def render_POST(self, request):
# Code here
class LoginResource(Resource):
isLeaf = True
def render_OPTIONS(self, request):
# Code here
def render_GET(self, request):
# Code here
def render_POST(self, request):
# Code here
class RefreshResource(Resource):
isLeaf = True
def render_OPTIONS(self, request):
# Code here
def render_GET(self, request):
# Code here
def render_POST(self, request):
# Code here
class HttpFactory(Site):
def __init__(self, resource):
# Code here
schema.py
#!/usr/bin/python
import graphene
import json
import sys, os
from main import factory
class Query(graphene.ObjectType):
# Code here
class Mutation(graphene.ObjectType):
# Code here
class Subscription(graphene.ObjectType):
# Code here
schema = graphene.Schema(query=Query, mutation=Mutation, subscription=Subscription)
main.py
import sys
from twisted.internet import reactor
from twisted.web.resource import Resource
from autobahn.twisted.resource import WebSocketResource
from ws_transport import WsProtocol, WsProtocolFactory
from web_transport import HttpResource, LoginResource, RefreshResource, HttpFactory
if __name__ == '__main__':
factory = WsProtocolFactory()
factory.protocol = WsProtocol
ws_resource = WebSocketResource(factory)
root = Resource()
root.putChild("", HttpResource())
root.putChild("login", LoginResource())
root.putChild("refresh", RefreshResource())
root.putChild(b"ws", ws_resource)
site = HttpFactory(root)
reactor.listenTCP(8000, site)
reactor.run()
Cheers,
Brian
I know this is not necessarily the answer you need. But I ran into the same problem and for me it meant that I have structured the project wrong. Meaning main.py or schema.py do things they are not meant to do. Of course you made the project so you get to decide what does what but what I mean is that maybe you should abstract even more. Since I do not quite understand what you want to do with the code since I do not know the libraries.
A simple hacky sort of thing is to just create another file called maybe run.py that then imports both files and dependency-injects main into schema or the other way around.
Another not-so-nice solution is to create an init() function that then imports the other file after the rest has been initialized and therefore insures that the import is only executed once.
But what I did was to check conceptually why main needed to import (in your case) schema and then ask myself is this really what main.py is supposed to do. If e.g. your main needs to provide templates to all other modules then why not create a templates.py or modules.py? Or what in my case was better is to create a bus system. This could allow modules to share only the data needed when needed and exposes a general api. But of course this would only make sense if you only share information.
In conclusion: Usually when the application is well designed you never run into circular imports. When you do it is a sign that you should rethink about how you structure your program.
For Python functions you could do something like this.
def functionInSchema(objectParameter):
# Add in code
This will give you access to the object from the main.py file.
To access attributes/methods use the parameter name, a dot and the attribute/function name.
def functionInSchema(objectParameter):
attribute = objectParameter.attribute
objectParameter.method()
Then in main.py you pass the Class instance (Object) as a parameter.
objectInMain = Class(param1, param2)
functionInSchema(objectInMain)
If you need access to the class in schema.py you can use the following import.
from main.py import ClassName
I hope this helped you! Good luck!

Categories