研究メモ:データ構造の話など

いまDjangoで作っている、タグがつけられる機能がついたフォーラムの実装で悩む。

作っていて、それなりに完成してきたかなーと思っていて、今日はとあるデータ構造に悩んでしまいました。
簡単にいうとSBMでつけられるものがWEBサイトじゃなくてレス単体という形のものです。
タグ付け出来る範囲としては、一つのレスに対してで、それにレスの作成者と、見ているユーザーという位置でして。

本題

レスにつけれられるタグは以下のものが二つ

  • レスに
    1. レス作成者があらかじめ付けたタグ
    2. ユーザごとに付けたタグ

今のところ、それの表示は別々に処理しているけど、

  1. Res一対多でTagsに関連づけから
    • リレーションから表示(resobj.tags.all()で)
  2. レスとタグとユーザで纏めたテーブルで管理
    • UATからレスの情報。さらにタグも引っ張ってきて、データを纏めて辞書化している
  • models.pyの一部分
from django.contrib.auth.models import User

class Tags (models.Model):
	tagword = models.CharField(max_length=200, unique=True)
	datetime = models.DateTimeField("Tags_datetime", auto_now_add=True)

	def __unicode__(self):
		return self.tagword + "/" + str(self.datetime)

class Threads (models.Model):
	title = models.CharField(max_length=200)
	datetime = models.DateTimeField("Threads_date_pub")
	user = models.ForeignKey(User)
	c_ipaddress = models.IPAddressField()
	last_edit = models.DateTimeField("last_edit_datetime")

	def __unicode__(self):
		return self.title + "/" + str(self.datetime)

class Res (models.Model):
	title = models.CharField(max_length=200)
	user = models.ForeignKey(User)
	datetime = models.DateTimeField("Res_datetime")
	textbody = models.TextField()
	ipaddress = models.IPAddressField()
	last_edit = models.DateTimeField("last_edit_datetime")

	#tags = models.ManyToManyField(Tags) #後にUAT側に移植
	threads = models.ForeignKey(Threads)

	def __unicode__(self):
		return self.title

class UserAddTags (models.Model): #UATと省略
	user = models.ForeignKey(User)
	tags = models.ManyToManyField(Tags)
	res = models.ForeignKey(Res)

	datetime = models.DateTimeField("useraddtag_datetime")
	last_edit = models.DateTimeField("last_edit")

	def __unicode__(self):
		return "id:" + str(self.id) + "/" + str(self.datetime)


で、2.をテンプレートに渡そうとするデータはこういうもので

[
 {"res" : resobject1 , "tags" : [{"tag1": 1 },{ "tag2" : 3 }, ]},
 {"res" : resobject2 , "tags" : [{"tag3": 4 },{ "tag4" : 2 }, ]},
]

こうすることで、これ一つでレスもタグも表示出来るようにしてみまして
tag1とかのキーに対する値は、各ユーザーが付けたタグの個数をカウントしたものです(tag 1はすべてのユーザをあわせて1つ付けられている)
これをテンプレートで処理、個数を文字の大きさに適応してタグクラウドっぽく見せようという魂胆。
ユーザがらみで関係なく、レスに関係しているすべてのUATのタグを引っ張るとこれで無難に動いてくれたのでよしとして。

余分なリストが出来てる

このほうがわかりやすいかな

[
 {"res" : resobject1 , "tags" : {"tag1": 1 , "tag2" : 3 } },
 {"res" : resobject2 , "tags" : {"tag3": 4 , "tag4" : 2 } },
]

なんでtagsの値をリストにしたんだろう?

ユーザ単位で絞ったりする場合は

2.を作成するときにfilterで絞ればいいかな
今時点で、この構造を作成する関数は、UATを引っ張るときにall()を使ってるから、ユーザでfilterすることにして
今後やるかもしれないAjaxでの動的に消す場合は・・・よくわからんw

ふと気がつくこと

UATはユーザ単位での管理も出来るはずなので、Resについてるタグの関係性もそれに置き換えるべきかも。
レス作成者はUATでつけたとしても同じ名前だし。ただ特別視したりする場合はどうしようか。
たとえば、タグクラウド内で青いのがそのほかのユーザだとして作成者のタグは赤くするとか、別々に分けて表示させるといったものなどなど
フラグを付けて、テンプレートで判断させるという方法ぐらいしか思いついてませんが、あとでやってみよう。
それをやるとしたらタグと個数のほかにフラグがあるから、また変な構造になりそうですね。テンプレート書くのの面倒だな・・・

##候補1
[
 {"res" : resobject1 , "tags" : [{"tag1": 1 }, "resauther" ], [{ "tag2" : 3 }, "no" ]},
 {"res" : resobject2 , "tags" : [{"tag3": 4 }, "no"], [{ "tag4" : 2 }, "no"]},
]
追記 2009/04/03 21:28:26

で、フラグを作るために、ビュー関数で

  • レス全体のUATのタグ
  • レス作成者がつけたUATのタグ

を比較して、レス作成者のUATタグの要素にフラグをつけるものを書いていて、比較ってどうやるんだでずいぶん躓いてがっくりきていて、お風呂に入って考えてたら、ああモデルのほうにフラグ書いとけばいいじゃんということになって、ずいぶんすっきりとしたお風呂上り。

2009/04/05 01:16:35

結局はこういう形にした。

[
 {"res" : resobject1 , 
  "tags" : 
   [{"tagword":"tag1", "count": 1L , "isauther":"yes" }, 
    {"tagword":"tag2", "count": 3L , "isauther":"no"}],
 }
 {"res" : resobject2 , 
  "tags" : 
   [{"tagword":"tag3", "count" : 4L , "isauther":"no"},
    {"tagword":"tag2", "count" : 2L , "isauther":"no"}],
 }
]

うん、ずいぶんすっきりした。タグの内容はすべてひとつの辞書としてまとめたし。これで呼び出し易くなった。
これを作るときに、今まではUATのテーブルから、タグの内容をforで全部書き出して、それをソートして重複なしにして、それからデータを成形していたけど
理想は、タグからリレーションしてそれぞれに関連する内容を取得する方法が良かったので、そのまま書いてみたらコードもずいぶん減ったじゃないか!
ちなみに、整理したコードはこれ

def res_uats_list(res_list): 
	res_uats_list = []

	for res in res_list :
		if not res.useraddtags_set.all() :
			res_uats_list.append({"res" : res , "tags" : ""},)
		else :
			#タグからリレーションで、レスに関連したものを引っ張り出す

			t = Tags.objects.filter(useraddtags__res=res.id)
			t1 = t.distinct().order_by("-tagword")
			t2 = t.order_by("-tagword")

			uat_andcount_list = []
			if t1 is None :
				res_uats_list.append({"res" : res , "tags" : ""},)
			else :
				uat_andcount_list = []
				for i in t1:
					tw = i.tagword
					try :	#UATにユーザの登録ある?
						i.useraddtags_set.get(user=res.user)
					except UserAddTags.DoesNotExist:
						uat_andcount_list.append({"tagword" : tw , "count" : t2.filter(tagword=tw).count(), "autheris" : "no"})
					else :
						uat_andcount_list.append({"tagword" : tw , "count" : t2.filter(tagword=tw).count(), "autheris" : "yes"})

				res_uats_list.append({"res" : res , "tags" : 
													sorted(uat_andcount_list , key=uat_andcount_list.index)})
	return res_uats_list