I have a Django model Adventure with two datetime fields:
from django.db import models
class Adventure(models.Model):
start_time = models.DateTimeField()
end_time = models.DateTimeField()
(...)
There's an instance of Adventure, let's say:
Adventure(start_time=TIME_1, end_time=TIME_2, ...)
Assuming there is some TIME_X between TIME_1 and TIME_2 (TIME_1 < TIME_X < TIME_2), how can I split one Adventure object into two separate objects, so after splitting I would have:
#first instance
Adventure(start_time=TIME_1, end_time=TIME_X, ...)
#second instance
Adventure(start_time=TIME_X, end_time=TIME_2, ...)
Adventure has more fields, which should be the same for both instances (except for PK, of course).
You could simply use orm methods to change one existing object and create only one new object:
# Get the first instance
adventure = Adventure.objects.get(start_time=TIME_1, end_time=TIME_2, ...)
middle_time = TIME_X
# save old end_time
old_end_time = adventure.end_time
# Modify first instance and save to DB
adventure.end_time = middle_time
adventure.save()
# Create new instance
adventure.pk = None
adventure.start_time = middle_time
adventure.end_time = old_end_time
adventure.save() # This will create a new object
As a result you will have two database entries with your other attributes the same except for the two start_time and end_time attributes.
You can do it with a method:
import copy
original_adventure = Adventure(start_time=TIME_1, end_time=TIME_2, ...)
adventure_split_1, adventure_split_2 = split_adventure(original_adventure, TIME_MIDDLE)
def split_adventure(adventure, middle_time):
adventure_split_1 = copy.deepcopy(adventure)
adventure_split_1.end_time = middle_time
adventure_split_2 = copy.deepcopy(adventure)
adventure_split_2.start_time = middle_time
return (adventure_split_1, adventure_split_2)
Related
I am using factory boy to generate data for my django application.
It is a tennis matches app which has player one and two as shown in below class. Either of it will be a winner which will be store in winner_one field.
I am getting some third player name in this field instead of player one or two. That player is also present in table.
Please advise what would be the best way to fix this?
class MatchFactory(factory.django.DjangoModelFactory):
class Meta:
model = Match
player_one = factory.SubFactory(UserFactory)
player_two = factory.SubFactory(UserFactory)
league = factory.Iterator(League.objects.all())
type = fuzzy.FuzzyChoice(Match.MATCH_CHOICES, getter=lambda c: c[0])
winner_one = random.choice([player_one, player_two])
start_date = fuzzy.FuzzyNaiveDateTime(
datetime.today() + relativedelta(months=1),
datetime.today() + relativedelta(months=3)
)
end_date = start_date
This one seems like a good use case of factory_boy's post generation hook.
class MatchFactory(factory.django.DjangoModelFactory):
class Meta:
model = Match
player_one = factory.SubFactory(UserFactory)
player_two = factory.SubFactory(UserFactory)
league = factory.Iterator(League.objects.all())
type = fuzzy.FuzzyChoice(Match.MATCH_CHOICES, getter=lambda c: c[0])
start_date = fuzzy.FuzzyNaiveDateTime(
datetime.today() + relativedelta(months=1),
datetime.today() + relativedelta(months=3)
)
end_date = start_date
#factory.post_generation
def winner_one(self, create, extracted, **kwargs):
if extracted:
self.winner_one = extracted
else:
self.winner_one = random.choice([self.player_one, self.player_two])
The issue stems from the random.choice([user_one, user_two]) declaration: that declaration is executed at module import time, not when running the factory.
At that point, both values are factory.SubFactory(UserFactory) — i.e a recipe for creating a new user.
Once random.choice() is called, it will return one of the declarations; your code is thus equivalent to:
class MatchFactory(factory.django.DjangoModelFactory):
...
player_one = factory.SubFactory(UserFactory)
player_two = factory.SubFactory(UserFactory)
winner_one = factory.SubFactory(UserFactory)
It picks 3 different users each time.
I would suggest going for the following pattern:
Add a "parameter" to decide which player should win;
Use that value to choose the right attribute:
class MatchFactory(factory.django.DjangoModelFactory):
class Meta:
model = Match
class Params:
# Decide who the winner is; that field won't be passed to the `Match` model.
winner = factory.fuzzy.FuzzyChoice([1, 2])
player_one = factory.SubFactory(UserFactory)
player_two = factory.SubFactory(UserFactory)
winner_one = factory.LazyAttribute(
lambda o: o.player_one if o.winner == 1 else o.player_two
)
With that pattern, you can also "choose" the winner when calling the factory:
MatchFactory(player_one=some_player, player_two=some_player, winner=1).
I am creating a program to move all individual quotes into a single quote matrix, using python and openpyxl, but when trying to load the model information it seems to be repeating the models from the first quote. Each quote is a separate excel file, it is confusing me as I'm having no problem getting the quote information, i.e. name, dates, etc. but the list of models is the same for every quote instead of pulling the models from each quote.
Data Structures:
class Model:
SKU = None
cost = None
qty = None
qty_remaining = None
class Quote:
name = None
number = None
start_date = None
end_date = None
models = []
Function to get data from each quote spreadsheet:
def load_quote(filename):
quote_wb = load_workbook(filename=filename, data_only=True, read_only=True)
quote_sheet = quote_wb.active
quote = Quote()
quote.number = quote_sheet['A2'].value
quote.number = quote.number[9:len(quote.number)]
quote.name = quote_sheet['A3'].value
quote.name = quote.name[14:len(quote.name)]
quote.start_date = quote_sheet['A6'].value
quote.start_date = quote.start_date[14:len(quote.start_date)]
quote.end_date = quote_sheet['A7'].value
quote.end_date = quote.end_date[12:len(quote.end_date)]
for row_vals in quote_sheet.iter_rows(min_row=12, max_row=250, min_col=1, max_col=9, values_only=True):
model = Model()
if row_vals[0] is not None:
model.SKU = row_vals[0]
model.cost = row_vals[4]
model.qty = row_vals[6]
model.qty_remaining = row_vals[8]
quote.models.append(model)
else:
continue
return quote
Each quote is formatted the same, with the quote number being in cell A2, name being in cell A3, start date in cell A4, and end date in cell 5.
From row 12 to 250 is each model in this format:
| SKU | Description | Cost | Original Quantity | Quantity Used | Quantity Remaining |
I do not need the Description or Quantity Used, so I ignore those. If the quote_sheet variable changes based on iterating through each file in a folder, and it is getting all the quote information from each file, why is it not getting the model information from each file?
Not sure if the function call is helpful but here that is as well:
quotes = []
for file in os.listdir(os.chdir(directory)):
if file.endswith(".xlsx"):
quote = load_quote(file)
quotes.append(quote)
All attributes of Quote and Model are defined on the class level - they are class attributes. Class attributes are shared across all instances of a class.
This is not a problem for immutable data like int or str, because by writing:
q1 = Quote()
q1.name = 'quote_name'
you essentially create a new instance attribute name bound to the instance q1 at runtime, which allows the instance to avoid looking up the Quote.name class attribute when you ask for its name.
However, since Quote.models is a mutable data (list), it is shared across all instances of Quote. If you append to that list, this in-place operation will be reflected across any instance of that class.
You can see this behavior with this short example:
class Quote:
name = None
number = None
start_date = None
end_date = None
models = []
q1 = Quote()
q2 = Quote()
q1.models.append('x')
print(q2.models) # output: ['x']
The easiest way to fix this is to turn Quote.models into a proper instance attribute, by defining it in the __init__ method. This way, each Quote instance is created with an unique, independent list attached to its models attribute:
class Quote:
name = None
number = None
start_date = None
end_date = None
def __init__(self):
self.models = []
This should be enough to fix your issue, although I'd really recommend turning all class attributes from Quote and Model into proper instance attributes - for the sake of clarity.
I have to define a function in order to duplicate an instance of my class Scenario.
I created an identical instance whose fields are identical except name and id, but when I save the new instance the original is deleted. Here is the code of the function:
def duplicate(scn):
attr_list = [field for field in Scenario._meta.get_fields() if isinstance(field,django.db.models.fields.Field)]
s = Scenario()
s.id = str(scn.id+10)
s.name='s_dup_of_' + scn.name
#print(Scenario.objects.all())
for a in attr_list:
if a.name!='name' and a.name!= 'id':
value = getattr(scn,a.name)
setattr(s,a.name,value)
#print(Scenario.objects.all())
#list_class = [Unit,HidroUnit,ThermalUnit,Arc,Flow,Fuel,Group,Loop,Machine,ModeVir,Node,Region,Reservoir,Solution,Station,Transitions,Tube]
list_class = [Unit,HidroUnit,ThermalUnit]
for myClass in list_class:
instance_list = myClass.objects.filter(scenario=scn)
attr_list = [field for field in myClass._meta.get_fields() if isinstance(field,django.db.models.fields.Field)]
for instance in instance_list:
inst_new = myClass()
for a in attr_list:
if a.name!='temporalobject_ptr':
value = getattr(instance,a.name)
setattr(inst_new,a.name,value)
inst_new.save()
#print(Scenario.objects.all())
s.save()
#print(Scenario.objects.all())
return None
For example, if I want to duplicate the scenario called s1 and to create the scenario s_dup_s1, and if I reproduce all lines of my duplicate function, then after I write s_dup_s1.save() the old instance s1 is deleted.
What can I do if I want s1 and s1_sup_s1 at the same time?
This is what happens when I run the code:
This doesn't work either:
Here's an easy hack to duplicate your instance:
You can rewrite your duplicate function as follows:
def duplicate(instance):
duplicate = Scenario.objects.get(pk=instance.pk)
duplicate.pk = None
duplicate.save()
return duplicate
This way your initial instance is preserved and you can do whatever you want with it. And, the function duplicate creates a duplicate of your instance, saves it and returns it.
Finally I find a solution. If you want to duplicate an instance s1 you have to set a new name and a new id and save it:
s1.id = scn.id+1
s1.name = scn.name + '_dup'
s1.save()
Of this way you will have both s1 and s1_dup.
The next works:
def duplicate(scn):
scn.id = scn.id+1
scn.name = scn.name + '_dup'
scn.save()
n = Scenario.objects.all().count()
s_new = Scenario.objects.all()[n-1]
list_class = [Unit,HidroUnit,ThermalUnit]
for myClass in list_class:
instance_list = myClass.objects.filter(scenario=scn)
attr_list = [field for field in myClass._meta.get_fields() if isinstance(field,django.db.models.fields.Field)]
for instance in instance_list:
instance.scenario = s_new
instance.num = instance.num + 1
instance.save()
return None
I am leveraging the SeriesHelper object of InfluxDB library(please have a look at https://influxdb-python.readthedocs.io/en/latest/examples.html#tutorials-serieshelper) to push set of data points to InfluxDB. The SeriesHelper class has to be inherited and the child class needs to initialize various objects as its meta attributes, so as to override the default values of the objects in the Parent class.
Actual code
class MySeriesHelper(SeriesHelper):
"""Instantiate SeriesHelper to write points to the backend."""
class Meta:
"""Meta class stores time series helper configuration."""
client = myclient
series_name = 'rf_results'
fields = ['some_stat', 'other_stat']
tags = ['server_name']
bulk_size = 5
autocommit = True
Here the 'series_name' object is initialized(hard-coded) right before it is ran as a script. My use case is to initialize 'series_name' based on the runtime arguments that are passed to this script.
I tried by defining a global variable whose value is providing at runtime and assigning that global variable to the 'series_name' like the below one, but in vain.
Problematic code
series_configured = None
class MySeriesHelper(SeriesHelper):
"""Instantiate SeriesHelper to write points to the backend."""
class Meta:
"""Meta class stores time series helper configuration."""
client = myclient
series_name = series_configured
fields = ['some_stat', 'other_stat']
tags = ['server_name']
bulk_size = 5
autocommit = True
def main():
global series_configured
series_configured = args.series_name
MySeriesHelper(server_name='server_A', some_stat='Master server', other_stat='Controller')
MySeriesHelper.commit()
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--series_name", dest='series_name',
help="The measurement to be used for storing the data points",required=True)
args = parser.parse_args()
main()
Error seen while running is
'NoneType' object has no attribute 'format'
It infers that the object 'series_name' is not initialized with a value. Is there any way of properly initializing it ?
When the python interpreter go over the code (line by line) it define all the classes static variable.
It's set static variable before you create an instance from a class.
That mean when you reach the point of:
autocommit = True
The value of series_name is already set to None (because that is the value of series_configured at the point).
The following example show that the static variable are already set before I created an instance:
>>> series_configured = None
>>> class MySeriesHelper:
"""Instantiate SeriesHelper to write points to the backend."""
class Meta:
"""Meta class stores time series helper configuration."""
series_name = series_configured
fields = ['some_stat', 'other_stat']
tags = ['server_name']
bulk_size = 5
autocommit = True
>>> print(MySeriesHelper.Meta.series_name)
None
If you want to change the Meta.series_configured static variable you will have to set it after the series_configured change its content.
Try the following main.
def main():
global series_configured
series_configured = args.series_name
# The following line will set the variable at the inner Meta class.
MySeriesHelper.Meta.series_name = series_configured
MySeriesHelper(server_name='server_A', some_stat='Master server', other_stat='Controller')
MySeriesHelper.commit()
I have the below code:
classes = [Class1(), Class2(), Class3()]
usedClasses = {}
number = random.randint(0, len(classes)-1)
usedClasses[len(usedClasses)] = classes[number]
This code randomly selects a class from classes and then adds this to usedClasses however, when I apply a method to a class in usedClasses, any duplicate additions are classed as the same instance. How would I add a class from classes to usedClasses as an instance each time.
You need to have a list of the classes themselves, not instances, and instatiate them when required:
classes = [Class1, Class2, Class3] # Just the names of the classes
usedClasses = {}
number = random.randint(0, len(classes)-1)
usedClasses[len(usedClasses)] = classes[number]() # create a new instance here.