تورهام 😳 | Django Tips – Telegram
تورهام 😳 | Django Tips
102 subscribers
4 photos
10 links
هیچی دیگه همچی داشت یادم میرفت گفتم اینجا بزارم قبل هر مصاحبه بخونم :)
Download Telegram
آپدیت کردن چند آبجکت به صورت همزمان در #جنگو

فریم‌ورک #django قابلیت آپدیت کردن دیتاها رو به روش ها مختلف داره که خیلی ها یا ازش بی خبر ان یا استفاده نمیکنن. بیایید ببینیم هر کدوم رو کجا استفاده کنی بهتره :)

مدل فرضی:

class Records(models.Model):
name = models.Charfield()
balance = models.InetegerField()
country =models.CharField()

خب فرض کنید ما یک هدیه به مناسب عید نو روز میخواییم به کاربرا بدیم، مثلا میخاییم نفری ۲ هزار تومن هدیه بدیم D:

حالا چند روش وجود داره.

روش اول ( نوب):
users = Records.objects.all()

for user in users:
user.balanc = user.balance + 2
user.save()

خیلی ساده و البته درب و داغون در خیلی جهات. مشکل اول اینه که ما رو همه کاربرا حلقه میزنیم و هر بار آپدیت رو روی کاربرا صدا میزنیم یعنی برای هر یوزر یک درخواست اپدیت به دیتابیس میره که اگه ۱ میلیون یوزر داشته باشیم ۱ میلیون درخواست میره :).

( تو اینه پست به اینکه باید از F استفاده کنید یا کانکارنسی و اینا هندل کنید اشاره نمیکنم، پست‌ها قبلی بخونید)

حالا روش بهتر چیه؟

روش بهتر:

user = Records.objects.update(balance=F("balance") + 2 )

همینقدر ساده :)


سناریو دوم: با بک‌اند یک بازی خفن رو داریم توسعه میدیم، داخل این بازی هر هفته یک ایونت اتفاق میوفته که افرادی که اون رو تموم کنن در آخر هفته یک تایتل به کنار اسمشون اضافه میشه و همچنین اگه امتیاز بالاتر از ۱۰ کسب کرده باشن به بالانس پول داخل گیم‌شون هم ۱۰۰ تا گلد اضافه میشه.


حالا بیایید فقط کوئری آپدیت این بهش ببینیم، فرض کنید این کوئری آخر هفته اجرا میشه. ( این فیلدا تو مدل فرض نداریم دیگه خودتون فرض کنید هست 😂❤️)

users = Records objects.fileter(done_weekly=True)

for user in users:
user.name = "Grunt " + user.name
if user.weekly_score >= 10:
user.balance = user.balance + 100

user.save()


خب همینطور که خیلی معلومه مشکلات فراوان داخلش هست. بزرگ‌ترین مشکلش اینه که هر بار برای هر کاربر یک درخواست آپدیت میدیم که میشهه همون مشکل بالا، آما آیا این بار میشه از روش بالا استفاده کرد و اینو فیکسش کرد؟ نه
روش بالا زمانی کاربرد داره که فیلدها قراره یک مقداری ثابتی به همشون داده بشه، اینجا بعضی ها ۱۰۰ تا گلد میگیرن بعضی ها نه پس کار نمیکنه، اینجا ما میتونیم از فانکشن bulk_update جنگو استفاده کنیم.

همون حلقه بالا رو میزنید با این تفاوت که داخلش .save رو صدا نمیزنید و تمام آبجکت‌ها رو داخل مموری آپدیت میکنید و بعد همچین حرکتی میزنید:

Records.objects.bulk_update(updated_users_list)

و تموم همرو با هم آپدیت میکنید با یک درخواست اینجا حتی میتونید یک قدم جلوتر برید و با اضافه کردن updated_fields به ورودی فانشکن و مشخص کردن اینکه دقیقا دوتا فیلد بالانس و نام فقط قرار آپدیت بشه بهترش کنید!

از این به بعد بهتر آپدیت کنید :)

@TorhamDevCH
👍1
سر این پروژه یکچیزی هم درباره جنگو یاد گرفتم.
مسئله: نیاز داریم یک فیلد به مدل دیفالت جنگو اضافه کنیم چون اورایت کردنش nmt حال ندارم.

جواب:

خب چندتا حالت میشه اینکار کرد، اول اینکه یک مدل جدا بزنید فارنکی با one2one فیلد کنید به مدل اصلی جنگو یا اینکه AbestractUser رو از خود جنگو ایمپورت کنید باهاش یک مدل جدید بسازید و فیلدا و هرچی میخوایید بهش اضافه کنید بعدش داخل تنظیمات بگید مدل ادمین جدید اینه.

من حرکت دوم زدم چون به نظرم اوکی بود.

نکته: روش‌های بالا همشون یکچیزی به مدل یوزر خود جنگو اضافه میکنن، نمیتونید ایطوری فیلدی ازش کم کنید(البته شاید بشه فیلدا اورایت کرد نمیدانم تست نکردم) و اینکه با این روش‌ها نمیتونید فانکشنالیتی مدل تغییر بدید تهش فانکشنالیتی جدید اضافه کنید مثلا نمیتونید set_password تغییر بدید. برای اون کار باید بزنید از base خودتون یک مدل یوزر بسازید به جنگو معرفی کنید :) فقط یک سری فیلد مشخص مثل is_staf داخلش باید باشه تا جنگو بتونه باهاش کنار بیاد.

آره خلاصه :)

@TorhamDevCH
👍1
TorhamDev | تورهام 😳
رفرنس داکیومنت به این ماجرا داکیومنتش بخونید دانا و نابغه بشید:)
جالبه همین خط اول عکس اول بخونید، توصیه جنگو اینه که هر پروژه‌ای میزنید حتی اگه مدل دیفالت نیاز‌هاتون برطرف میکنه بهتر مدل خودتون بسازید و معرفی کنید‌.
👍1
Django QuerySet - only() vs defer() vs exclude()

If you have some fields in your Django model that contain a lot of data and you don't need those fields for a particular query, you can tell Django not to retrieve them with defer():
Event.objects.defer("denoscription")
While defer() works at the attribute level, exclude() works on the row level.
In other words, exclude() arguments are used after the WHERE clause -- i.e., SELECT * FROM users WHERE name = 'jan' -- while defer() changes * to the provided fields -- i.e., SELECT name, email FROM users.
Opposite to defer() is only(). If you have fewer fields that you want to retrieve than those you don't, you can use only() to retrieve only the fields provided as arguments:

Event.objects.only("noscript")
👍2
QuerySet.contains(obj)
👍2
نکته رندوم درباره orm #جنگو

@TorhamDevCH
👍1
TorhamDev | تورهام 😳
نکته رندوم درباره orm #جنگو @TorhamDevCH
ماجرا اینه که زمانی که شما objects.get میزنید درحقیقت دارید همچین کوئری میسازید:

SELECT * FROM MyModel WHERE id=1


و بعد وقتی پشت سرش .delete() میزنید اتفاقی که میوفته اول کوئری select رو میزنه دیتا رو میگیره و بعد دوباره کوئری delete رو میزنه.


تو حالت دوم که از filter استفاده میکنید دیگه یک راست میاد کوئری دلیت رو میزنه و دیگه گت نمیکنه در نتیجه اگر دیتا وجود هم نداشته باشه اروری نمیخورید چون خوب اصلا نگرفتیدش که ارور بخورید. در نهایت اگه تمام کارتون از یک دیتا اینه که حذفش کنید با filter.delete بزنید خیلی بهتره.


@TorhamDevCH
👍5
امروز یکسری حالات مختلف از teardown کردن تست‌ها در pytest یاد گرفتم خوب بود هر کدوم کاربرد و جای خودش داره.

به سه حالت مختلف رسیدم. حالت اول زمانی که شما نیاز به یک resource دارید برای تست‌هاتون برای مثال اول لاگین کنید و بعدش ریکوئست بزنید. تو این مواقع بهتر که یک fixture داشته باشید که براتون لاگین کنه و وقتی کارتون تموم شد خودش لاگ اوت کنه.

به اون پروسه لاگ‌اوت کردن میگن teardown یکدونه هم tear up داریم که پروسه لاگین کردن تو این مثاله. مقال دیگش میشه زمانی که نیاز دارید یک رکورد خاص داخل دیتابیس ساخته بشه و بعد از تست حذف بشه. به ساختنش میگن tear up به حذف کردنش میگن tear down.

خب حالا حال اول که fixture باشه.

import pytest

@pytest.fixture
def client() -> AuthedClient:
#login and etc
yeild client
client.logout()

داخل فیکسچر‌ها pytest هرچیزی که بعد از yield بیارید teardown و هرچی که قبلش بیاد tearup.


حالت دوم شما یک مقدار از درون تست نیاز دارید برای tear down کردن. برای مثال شما یک تست دارید پست زدن داخل توییتر رو تست میکنه. شما برای teardown کردن این تست لازم دارید پست رو پاک کنید اما برای پاک کردنش نیاز به id اون پست دارید. اینجاس که شما یک مقدار لازم دارید که داخل خود تست‌ ساخته شده.
برای این مورد به نظر من بهترین حالت در حال حاظر با دانش الان من استفاده از try-finally هستش.

def test_twt_post_create_success(twt_client):
post_id = None
try:
post = twt_client.post("Hello from test")
post_id = post.id
finally:
if post_id:
twt_client.remove_post(post_id)

اینجا فیکسچر‌های شما که برای مثال twt_client هست براتون کلاین tear up و tear down میکنن به روش اول. و try-finally پستی که ساختید رو tear down میکنه. مهدی سینیور ما باشد گفت که یک فیکسچر بسازم که داخلش یک yeild خالی باشه و بعد از yeild از داخل یک متغیر گلوبال بیاد ایدی پست ها رو بخونه و همرو حذف کنه که برای این کار لازمه داخل هر تست هر وقت پست ساختیم اضافه اش کنید به اون متغیر که من به نظرم try finally بهتر بود در نتیجه همون زدم فرستادم تک‌لید :)

@TorhamDevCH
👍1
یک چیز جالب دیروز درباره pytest خوندم داخل داکیومنتش و اینه که شما میتونید scope یک fixture رو داینامیک کنید نسبت به تستی که اجرا میکنید.

def determine_scooe(fixture_name, config):

if config.getoption("--keep-containers", None)
return "session"
return "function"


@pytest.fixture(scope=datermine_scope)
def docker_container():
yield spwan_container()

این مثال خود داکیومنت و همینطوری که میبینید اگه یک فانکشن(callable) به جای اسکوپ بدید پای‌تست اون اجرا میکنه و دوتا ورودی بهش میده و از خروجی اون برای scope استفاده میکنه.

@TorhamDevCH
یک چیز دیگه هم چند روزه میخواستم بنویسم دربارش. درباره functions.wraps

زمانی که یک دکوریتور مینویسید اگر از wraps استفاده نکنید باعث میشید سیگنچر فانکشن‌هایی که از دکوریتور استفاده میکنن تغییر کنه.

def logged(func):
def with_logging(*args, **kwargs):
print(func.__name__ + " called")
return func(*args, **kwargs)

return with_logging
`

وقتی شما از این دکوریتور استفاده میکنید برای مثال:
@logged
def foo(x):
return x ** x

در حقیقت دارید میگید
def foo(x):
return x ** x

foo = logged(foo)


حالا اتفاقی که میوفته اینه که سیگنچر foo تغییر میکنه به logged یعنی اگر شما داک استرینگ foo بگیرید بعد دکوریت شدن توسط logged چیزی که خواهید دید داک استرینگ logged. میتونید داک استرینگ رو با داندرلاین doc بگیرید.

حالا اگر از @wraps استفاده کنید این اتفاق نمیوفته و سیگنچر فانکشن foo باقی خواهد موند.

from functools import wraps

def logged(func):
@wraps
def with_logging(*args, **kwargs):
print("logged")
return func(*args, **kwargs)

@logged
def foo(x):
return x * x

اره خلاصه

@TorhamDevCH
1